From 6e17e76436ead5bd03a19e2c3fef2406cccdb504 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Wed, 23 Sep 2026 14:54:07 +0900 Subject: [PATCH 01/59] fix: restore Android scheduled notification receivers --- .../android-notification-contract.yml | 96 ++ .github/workflows/flutter_test.yml | 4 + android/app/src/main/AndroidManifest.xml | 14 + ...roid-Notification-Delivery-Verification.md | 63 + docs/App-Improvement-Audit-2026-09-23.md | 188 +++ plans/audit-2026-09-23/README.md | 82 + plans/audit-2026-09-23/issues/A01.md | 124 ++ plans/audit-2026-09-23/issues/A02.md | 161 ++ plans/audit-2026-09-23/state.json | 1384 +++++++++++++++++ .../test_android_notification_manifest.py | 120 ++ tool/check_android_notification_manifest.py | 102 ++ 11 files changed, 2338 insertions(+) create mode 100644 .github/workflows/android-notification-contract.yml create mode 100644 docs/Android-Notification-Delivery-Verification.md create mode 100644 docs/App-Improvement-Audit-2026-09-23.md create mode 100644 plans/audit-2026-09-23/README.md create mode 100644 plans/audit-2026-09-23/issues/A01.md create mode 100644 plans/audit-2026-09-23/issues/A02.md create mode 100644 plans/audit-2026-09-23/state.json create mode 100644 test/tool/test_android_notification_manifest.py create mode 100644 tool/check_android_notification_manifest.py diff --git a/.github/workflows/android-notification-contract.yml b/.github/workflows/android-notification-contract.yml new file mode 100644 index 00000000..2fd55cef --- /dev/null +++ b/.github/workflows/android-notification-contract.yml @@ -0,0 +1,96 @@ +name: Android Notification Contract + +on: + workflow_dispatch: + pull_request: + branches: [main, 'release/**', 'hotfix/**'] + push: + branches: [main, 'release/**', 'hotfix/**'] + +permissions: + contents: read + +concurrency: + group: android-notification-contract-${{ github.ref }} + cancel-in-progress: true + +jobs: + release-manifest: + runs-on: ubuntu-latest + timeout-minutes: 35 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + - uses: subosito/flutter-action@v2 + with: + flutter-version: '3.44.4' + channel: stable + cache: true + - name: Verify source receiver contract and negative fixtures + run: python3 -B -m unittest discover -s test/tool -p 'test_*.py' -v + - name: Install packages and generate local outputs + run: | + flutter pub get + dart run build_runner build --delete-conflicting-outputs + dart run tool/check_generated_dart_policy.dart + dart run tool/check_local_only_boundary.dart + - name: Create disposable validation signing key + run: | + keytool -genkeypair -noprompt \ + -keystore "$RUNNER_TEMP/notification-validation.jks" \ + -alias notification-validation -keyalg RSA -keysize 2048 -validity 2 \ + -dname 'CN=OnTime CI Validation Only' \ + -storepass validation-only -keypass validation-only + echo "ANDROID_KEYSTORE_PATH=$RUNNER_TEMP/notification-validation.jks" >> "$GITHUB_ENV" + - name: Build release configuration for validation only + env: + ANDROID_KEYSTORE_PASSWORD: validation-only + ANDROID_KEY_ALIAS: notification-validation + ANDROID_KEY_PASSWORD: validation-only + run: flutter build apk --release + - name: Inspect manifest packaged inside APK + run: | + mkdir -p artifacts/android-notification-contract + "$ANDROID_HOME/cmdline-tools/latest/bin/apkanalyzer" manifest print \ + build/app/outputs/flutter-apk/app-release.apk \ + > artifacts/android-notification-contract/apk-manifest.xml + python3 tool/check_android_notification_manifest.py \ + artifacts/android-notification-contract/apk-manifest.xml + - name: Verify merged manifests and record artifact identity + run: | + python3 - <<'PY' + import hashlib, json, os, shutil, subprocess + from pathlib import Path + paths = sorted(Path('build/app/intermediates').glob('merged_manifest*/release/**/AndroidManifest.xml')) + if not paths: + raise SystemExit('No release merged manifest was produced') + subprocess.run(['python3', 'tool/check_android_notification_manifest.py', *map(str, paths)], check=True) + evidence = Path('artifacts/android-notification-contract') + for index, path in enumerate(paths): + shutil.copyfile(path, evidence / f'merged-manifest-{index}.xml') + apk = Path('build/app/outputs/flutter-apk/app-release.apk') + record = { + 'commit': os.environ['GITHUB_SHA'], + 'artifact': str(apk), + 'sha256': hashlib.sha256(apk.read_bytes()).hexdigest(), + 'signing': 'disposable CI validation key; not a store release', + 'merged_manifests': [str(path) for path in paths], + 'device_delivery_verified': False, + } + (evidence / 'identity.json').write_text(json.dumps(record, indent=2) + '\n') + print(json.dumps(record, indent=2)) + PY + - name: Upload validation evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: android-notification-contract-${{ github.sha }} + path: artifacts/android-notification-contract/ + if-no-files-found: warn + retention-days: 30 + - name: Remove disposable key + if: always() + run: rm -f "$RUNNER_TEMP/notification-validation.jks" diff --git a/.github/workflows/flutter_test.yml b/.github/workflows/flutter_test.yml index c1ccf182..4ebd3b3a 100644 --- a/.github/workflows/flutter_test.yml +++ b/.github/workflows/flutter_test.yml @@ -16,6 +16,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Verify Android notification manifest contract + run: | + python3 -B -m unittest discover -s test/tool -p 'test_*.py' -v + python3 tool/check_android_notification_manifest.py android/app/src/main/AndroidManifest.xml - uses: subosito/flutter-action@v2 with: flutter-version: "3.44.4" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index cecb5844..c5324e06 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -62,6 +62,20 @@ android:name=".NativeAlarmReceiver" android:enabled="true" android:exported="false" /> + + + + + + + + + + FGjLu#rvNsE# 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

yf$KJhk@?Y(u1aEUXlWnNV0UbUIaCyI8rohagWh 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 atBj0u8?PM~-;D@KIe%8+Op=to=l=q?lqysJ literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/a14/focused.txt b/plans/audit-2026-09-23/artifacts/a14/focused.txt new file mode 100644 index 00000000..09d58b71 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a14/focused.txt @@ -0,0 +1,102 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: skips a just-missed alarm instead of scheduling catch-up +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:00 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:00 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:00 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:00 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: coalesces overlapping reconciliation requests +00:00 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:00 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:00 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:00 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:00 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:00 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:00 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:00 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:00 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:00 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:00 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:00 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:00 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:00 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:00 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:00 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:00 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:00 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:00 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:00 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native revoke and grant switch providers after cancelling the old registration +00:00 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android read failure is unknown, not absent +00:00 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:00 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cancel needs public success and post read-back +00:00 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:00 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache absence does not hide cancel failure +00:00 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:00 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android remaining ID and failed post query are unconfirmed cancellations +00:00 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:00 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS OS observation preserves source and minimal ownership +00:00 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:00 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:00 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cancel needs public success and post read-back +00:00 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:00 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cache absence does not hide cancel failure +00:00 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:00 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:00 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit observation rejects unavailable, malformed and unrelated IDs +00:00 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit observation rejects unavailable, malformed and unrelated IDs +00:00 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:00 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit already absent is idempotent; present cancel requires fresh absence +00:00 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit already absent is idempotent; present cancel requires fresh absence +00:00 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed native write cleanup forbids fallback and keeps cancellation identity +00:00 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:00 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: off retains a cleanup warning and denial never becomes precise +00:00 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: no future registrations never becomes precise from capability +00:00 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: iOS alarm and notification language remains capability appropriate +00:00 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=false snapshot after persistence +00:00 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=true snapshot after persistence +00:00 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=false snapshot after persistence +00:00 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=true snapshot after persistence +00:00 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: legacy and corrupted content cannot bypass reconciliation at either provider +00:00 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancel uses the plugin API for pending and presented IDs, never cancelAll +00:00 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancellation errors are visible to reconciliation +00:00 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:01 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: detailed notification opt-in persists locally and reconciles +00:01 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: disabling schedule delivery cancels every local registration +00:01 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: disabled delivery still shows unconfirmed cleanup and clears after recovery +00:01 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: fallback permission enables local schedule notifications +00:01 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: armed local notification is reported without server status +00:01 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: timing education is optional once and settings require explicit choice +00:01 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: education setting choice rereads state without claiming a grant +00:01 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: current timing grant does not hide partial scheduling or display denial +00:01 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: late education cannot appear after leaving the eligible route or consume once marker +00:01 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: data replacement during timing lookup cannot consume education or open settings +00:02 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: authorized notification permission reports it is already on +00:02 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: new notification grant initializes local notifications +00:02 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: restricted notification state can open system settings +00:02 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: local data and bundled privacy destinations remain in-app +00:02 +100: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a14/full.txt b/plans/audit-2026-09-23/artifacts/a14/full.txt new file mode 100644 index 00000000..d4b80457 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a14/full.txt @@ -0,0 +1,778 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_data_operation_gate_test.dart: rejects competing operations instead of queueing destructive work +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_data_operation_gate_test.dart: failure releases lease; invalidated database cannot be reused +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/database_index_test.dart: creates lookup indexes for schedule and preparation DAO predicates +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/database_index_test.dart: rejects in-place upgrades across the one-way local cutover +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: successful bootstrap does not remove runtime +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: failure before Auth attempts cleanup and preserves original failure +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: cleanup failure preserves both failures without private message text +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:01 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:01 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:02 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:02 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:02 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:02 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:02 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:02 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:02 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:03 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:03 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:03 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:03 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:03 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:03 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:03 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:03 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:03 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:03 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:03 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:04 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:04 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: explicit exact denial retries once and returns actual approximate receipt +00:04 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unrelated provider error is never converted to approximate success +00:04 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: failed approximate retry stays failed and is not retried again +00:04 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unknown timing permission uses approximate rather than claiming exact +00:04 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: display denial blocks scheduling even with exact access +00:04 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: settings response is not proof of a grant: permission is reread +00:04 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: iOS timing access is unsupported without calling Android plugin +00:04 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:04 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:04 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:04 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: loadAll returns the current local registry +00:04 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: upsert replaces an existing schedule record and keeps others +00:04 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: deleteByScheduleId removes only the requested schedule +00:04 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: replaceAll deduplicates by schedule id and deleteAll clears storage +00:04 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/early_start_session_repository_impl_test.dart: markStarted saves and getSession restores early-start timestamp +00:04 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:04 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:05 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:05 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:05 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:05 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk changes preserve only overridden fields and preparation +00:05 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: ending following leaves active and historical rows and their preparations intact +00:05 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: finite conflicts require explicit exclusion and excluded slots consume count +00:05 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: place edits never rename another occurrence or historical place +00:05 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk edit preserves active preparation and consumes its original count +00:05 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: stale first occurrence approval asks for review without changing durable data +00:05 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:05 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:05 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:05 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:05 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:05 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:05 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:05 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:05 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:05 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:05 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:05 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:05 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: spare time preserves preferences, scores and export metadata +00:05 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: repeated initial creation cannot replace an existing profile +00:05 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: same preferences and empty score reset are exact no-ops +00:05 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:05 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: concurrent finishes count distinct schedules once and duplicate only once +00:05 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:05 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:05 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:05 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: multiple preparation steps are a no-op even when caller omits links +00:05 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: missing profile rejects updates and never reports a successful mutation +00:05 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: empty profile starts without a durable revision and creation is race safe +00:05 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: raw revision update notifies DB watchers and failed profile write publishes no success +00:05 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_data_source_contract_test.dart: schedule local data source contract does not expose domain entities +00:06 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:06 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:06 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:06 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:06 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:06 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:06 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:06 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:06 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: saves and loads the early start timestamp per schedule +00:06 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:06 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:06 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:06 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers map schedules to and from database rows preserving stored fields +00:06 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:06 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps users to and from database rows preserving profile values +00:06 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers empty users cannot be converted to database rows +00:06 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps places and preparation steps to database rows +00:06 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:06 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getUserById returns null for an unknown user +00:06 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getAllUsers returns all persisted users as domain entities +00:06 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:06 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return an empty preparation list when user has no steps +00:06 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:06 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps when rows are out of order +00:06 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should keep remaining preparation steps when a link is broken +00:06 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return each preparation step once when links form a cycle +00:06 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:06 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:06 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:06 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:06 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:06 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:06 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:06 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:06 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:06 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getScheduleById should return a schedule from the database of given [id] +00:06 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:06 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:06 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:07 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] +00:07 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: updatePreparationUser should update a preparation step +00:07 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null +00:07 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations createPreparationSchedule stores steps as an ordered linked list +00:07 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:07 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:07 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:07 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:07 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:07 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById returns the stored step contract +00:07 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById returns the stored step contract +00:07 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: last day is distinct from 31st, including leap February +00:07 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:07 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:07 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:07 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: daily intervals keep civil time and include the end date +00:07 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:07 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:07 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:07 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: new series starts at a future preparation and counts from there +00:07 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:07 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:07 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:07 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:07 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:07 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:07 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:07 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation +00:07 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:07 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:07 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: skips a just-missed alarm instead of scheduling catch-up +00:07 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:07 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:07 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:07 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:07 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:07 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:07 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:07 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:07 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:07 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:07 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:07 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:07 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:07 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:07 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:07 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: coalesces overlapping reconciliation requests +00:07 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:07 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:07 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:07 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:07 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:07 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:07 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:07 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:07 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:07 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:07 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:07 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:07 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:07 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:07 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:07 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:07 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:07 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:07 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:07 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:07 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:07 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: deleted and finished schedules cancel targeted alarm before reconciling +00:07 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: reconciliation failures remain best-effort +00:07 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:07 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:07 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:07 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:07 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:07 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:07 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:07 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:07 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:07 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:07 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:07 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:07 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:07 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:07 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:07 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:07 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:07 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:07 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:07 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:08 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/cancel_alarms_use_cases_test.dart: CancelScheduleAlarmUseCase cancels matching native and fallback records +00:08 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:08 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:08 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:08 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:08 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:08 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:09 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: a previous civil date in a negative offset still supplies a future alarm +00:09 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:09 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: empty user exposes null convenience values +00:09 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:09 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:09 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:09 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:09 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:09 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:09 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:09 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:09 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:09 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:09 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities fast-forwards preparation steps for late entry +00:09 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities single preparation step tracks elapsed time and diagnostics +00:09 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities marks all steps done for very late entry inside schedule window +00:09 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports display helpers for current and completed steps +00:09 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities skipCurrentStep marks only the active step done +00:09 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities completed and empty preparations expose safe display fallbacks +00:09 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities cache fingerprint changes when preparation step name changes +00:09 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities schedule with preparation derives start time and total duration +00:09 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities combines a schedule entity with timed preparation preserving fields +00:09 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleEntity diagnostics string representation includes schedule identity for diagnostics +00:09 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleState timing helper durationUntilPreparationStartAt uses injected now deterministically +00:09 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:09 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:09 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: minimal progress hydrates names/durations from current source only +00:09 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy exact fingerprint additionally requires all step fields +00:09 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy launch becomes a minimal versioned route, never a start command +00:09 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:09 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:09 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:09 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:09 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:10 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (setUpAll) +00:10 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:10 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:11 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:11 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:11 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:11 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:11 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:11 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:11 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: scheduled days render without hiding the selected day +00:11 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:11 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:11 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: header arrows move between months and request month loads +00:11 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:11 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:11 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:11 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:11 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:11 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:11 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:12 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:12 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:12 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:12 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:12 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:12 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:12 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:12 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:12 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: tile text updates when schedule fields change for same id +00:12 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: tile text updates when schedule fields change for same id +00:12 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:12 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:12 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:12 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:12 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:12 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:12 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:12 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden after preparation start time +00:12 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:12 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:12 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:13 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:13 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:13 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:13 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:13 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:13 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:13 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow emits ongoing when now is inside preparation window +00:13 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:13 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:13 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:13 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:13 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:13 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:13 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow restoring an ongoing run derives current step from start time skip action and now +00:13 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:13 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:13 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:13 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:13 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:13 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:13 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm launch start action keeps cached schedule on start prompt +00:13 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:13 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:13 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:13 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:13 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:13 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:14 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:15 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +549: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +550: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +551: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:15 +552: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:15 +553: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:15 +554: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:15 +555: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:15 +556: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:15 +557: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:15 +558: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:15 +559: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:15 +560: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:15 +561: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:15 +562: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:15 +563: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:15 +564: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:15 +565: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:15 +566: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: date picker saves the initial date value +00:16 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (setUpAll) +00:16 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:16 +568: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:16 +569: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:16 +570: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:16 +571: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:16 +572: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:16 +573: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:16 +574: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:16 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (tearDownAll) +00:16 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: (setUpAll) +00:16 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:16 +576: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +577: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +578: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +579: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +580: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +581: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +582: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +583: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +584: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +585: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +586: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +588: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:17 +589: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:18 +590: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:18 +591: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:18 +592: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:18 +593: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:18 +594: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:18 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:18 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:18 +597: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:18 +598: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:19 +599: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:19 +600: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:19 +601: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:19 +602: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:19 +603: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:19 +604: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:19 +605: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:19 +606: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:19 +607: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:19 +608: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:19 +609: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:19 +610: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:19 +611: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: changed preparation step time is submitted +00:19 +612: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: timing education is optional once and settings require explicit choice +00:19 +613: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: timing education is optional once and settings require explicit choice +00:19 +614: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: timing education is optional once and settings require explicit choice +00:19 +615: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: timing education is optional once and settings require explicit choice +00:19 +616: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: timing education is optional once and settings require explicit choice +00:19 +617: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:19 +618: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:20 +619: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:20 +620: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:20 +621: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:20 +622: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:20 +623: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:20 +624: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +626: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +627: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +628: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +629: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +630: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +632: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +633: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +634: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +635: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +636: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:20 +637: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:21 +638: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:21 +639: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:21 +640: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:21 +641: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:21 +642: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:21 +643: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:21 +644: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:21 +645: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:22 +646: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:22 +647: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:22 +648: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:22 +649: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:22 +650: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets close button confirms before leaving start prompt +00:22 +651: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:22 +652: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:22 +653: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets early-start start-now button navigates to alarm +00:22 +654: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets early-start start-now button navigates to alarm +00:22 +655: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: dismiss keeps notification delivery when fallback is available +00:22 +656: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets early-start not-now button navigates home +00:22 +657: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets deprecated five-minute flag resolves to early-start choices +00:22 +658: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets early-start variant is shown with dedicated prompt +00:22 +659: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets early-start variant formats hour and minute lead time +00:22 +660: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets early-start primary action starts preparation and navigates +00:22 +661: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:22 +662: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:22 +663: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:22 +664: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:22 +665: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:22 +666: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:22 +667: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:22 +668: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:23 +669: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:23 +670: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:23 +671: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:23 +672: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:23 +673: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:23 +674: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:23 +675: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:23 +676: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:23 +677: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:23 +678: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:23 +679: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:24 +680: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:24 +681: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:24 +682: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:24 +683: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:24 +684: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:24 +685: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:24 +686: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:24 +687: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: management reads own preparation and confirms ending following scope +00:24 +688: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: form review and cancel write nothing; confirmation saves owned preparation and alarms +00:24 +689: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:24 +690: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:24 +691: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:24 +692: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +693: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +694: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +695: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +696: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +697: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +698: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +699: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +700: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +701: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +702: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +703: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +704: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +705: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +706: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +707: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:24 +708: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:25 +709: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:25 +710: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:25 +711: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:25 +712: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:25 +713: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:25 +714: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:25 +715: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:25 +716: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:25 +717: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:25 +718: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:25 +719: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: fills the bottom safe area while keyboard is hidden +00:26 +720: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +721: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +722: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +723: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +724: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +725: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +726: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +727: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +728: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +729: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +730: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +731: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +732: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +733: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +734: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +735: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +736: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:26 +737: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: saves date and time picker selections through the cubit +00:26 +738: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:27 +739: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:27 +740: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: shows explicit choices for a repeated DST time +00:27 +741: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:27 +742: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:27 +743: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:27 +744: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:27 +745: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:27 +746: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:27 +747: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:27 +748: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:27 +749: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:27 +750: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:27 +751: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:28 +752: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:28 +753: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:28 +754: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:28 +755: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:28 +756: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:28 +757: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:28 +758: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:28 +759: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:29 +760: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:29 +761: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:29 +762: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:29 +763: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:29 +764: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:29 +765: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:29 +766: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:29 +767: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:29 +768: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation errors explain invalid name and preparation time +00:29 +769: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +00:29 +770: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +00:29 +771: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +00:29 +772: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a14/generation.txt b/plans/audit-2026-09-23/artifacts/a14/generation.txt new file mode 100644 index 00000000..72ab7afc --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a14/generation.txt @@ -0,0 +1,22 @@ +W These options have been removed and were ignored: --delete-conflicting-outputs + 0s freezed on 455 inputs; lib/core/backup/backup_crypto.dart +W SDK language version 3.12.0 is newer than `analyzer` language version 3.10.0. Run `flutter packages upgrade`. + 2s freezed on 455 inputs: 3 skipped, 1 no-op; spent 2s analyzing; lib/core/backup/recurring_backup_data.dart + 6s freezed on 455 inputs: 11 skipped, 4 no-op; spent 5s analyzing; lib/core/database/open_database.dart + 7s freezed on 455 inputs: 250 skipped, 62 no-op; spent 6s analyzing; lib/presentation/shared/router/route_arguments.dart + 7s freezed on 455 inputs: 372 skipped, 83 no-op; spent 6s analyzing + 0s json_serializable on 910 inputs; lib/core/backup/backup_crypto.dart + 1s json_serializable on 910 inputs: 709 skipped, 201 no-op + 0s drift_dev on 3640 inputs; lib/core/backup/backup_crypto.dart + 0s drift_dev on 3640 inputs: 721 skipped, 34 output, 170 same; lib/core/database/database.freezed.dart + 1s drift_dev on 3640 inputs: 3039 skipped, 67 output, 339 same, 195 no-op + 0s source_gen:combining_builder on 1820 inputs; lib/core/backup/backup_crypto.dart + 0s source_gen:combining_builder on 1820 inputs: 1801 skipped, 7 same, 12 no-op + 0s mockito:mockBuilder on 1008 inputs; test/core/backup/backup_crypto_test.dart + 0s mockito:mockBuilder on 1008 inputs: 24 skipped, 1 no-op; test/core/backup/backup_restore_picker_test.dart.types.temp.dart + 0s mockito:mockBuilder on 1008 inputs: 930 skipped, 78 no-op + 0s injectable_generator:injectable_builder on 4648 inputs; lib/core/backup/backup_crypto.dart + 0s injectable_generator:injectable_builder on 4648 inputs: 4447 skipped, 35 same, 166 no-op + 0s injectable_generator:injectable_config_builder on 4648 inputs; lib/core/backup/backup_crypto.dart + 0s injectable_generator:injectable_config_builder on 4648 inputs: 4565 skipped, 83 no-op + Built with build_runner/aot in 10s; wrote 448 outputs. diff --git a/plans/audit-2026-09-23/artifacts/a14/kotlin.txt b/plans/audit-2026-09-23/artifacts/a14/kotlin.txt new file mode 100644 index 00000000..fa39c04c --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a14/kotlin.txt @@ -0,0 +1,33 @@ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:117:20: warning: 'fun setDecorFitsSystemWindows(p0: Boolean): Unit' is deprecated. Deprecated in Java. + window.setDecorFitsSystemWindows(false) + ^^^^^^^^^^^^^^^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:136:44: warning: 'static field FLAG_DISMISS_KEYGUARD: Int' is deprecated. Deprecated in Java. + WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD, + ^^^^^^^^^^^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:143:67: warning: 'fun get(p0: String!): Any?' is deprecated. Deprecated in Java. + rawExtras?.keySet()?.associateWith { key -> rawExtras.get(key) }, + ^^^ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:337:23: warning: 'fun vibrate(p0: VibrationEffect!, p1: AudioAttributes!): Unit' is deprecated. Deprecated in Java. + vibrator?.vibrate( + ^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:463:58: warning: 'field scaledDensity: Float' is deprecated. Deprecated in Java. + val scaledDensity = resources.displayMetrics.scaledDensity + ^^^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt:257:59: warning: 'fun get(p0: String!): Any?' is deprecated. Deprecated in Java. + extras.keySet().associateWith { key -> extras.get(key) }, + ^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmReceiver.kt:271:14: warning: 'fun setPriority(p0: Int): Notification.Builder!' is deprecated. Deprecated in Java. + .setPriority(Notification.PRIORITY_MAX) + ^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmReceiver.kt:271:39: warning: 'static field PRIORITY_MAX: Int' is deprecated. Deprecated in Java. + .setPriority(Notification.PRIORITY_MAX) + ^^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmReceiver.kt:277:37: warning: 'constructor(p0: Int, p1: CharSequence!, p2: PendingIntent?): Notification.Action.Builder' is deprecated. Deprecated in Java. + Notification.Action.Builder( + ^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmReceiver.kt:321:59: warning: 'fun get(p0: String!): Any?' is deprecated. Deprecated in Java. + extras.keySet().associateWith { key -> extras.get(key) }, + ^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeLog.kt:37:59: warning: 'fun get(p0: String!): Any?' is deprecated. Deprecated in Java. + extras.keySet().associateWith { key -> extras.get(key) } + ^^^ diff --git a/plans/audit-2026-09-23/artifacts/a14/pre_test_environment_failure.txt b/plans/audit-2026-09-23/artifacts/a14/pre_test_environment_failure.txt new file mode 100644 index 00000000..5cd0edff --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a14/pre_test_environment_failure.txt @@ -0,0 +1,3 @@ +Flutter failed to create a link at "/Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/macos/Flutter/ephemeral/Packages/.packages/flutter_secure_storage_darwin-0.3.2" to "/Users/ejunpark/.pub-cache/hosted/pub.dev/flutter_secure_storage_darwin-0.3.2/darwin/flutter_secure_storage_darwin". The file or directory could not be found. +PathNotFoundException: Cannot create link, path = '/Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/macos/Flutter/ephemeral/Packages/.packages/flutter_secure_storage_darwin-0.3.2' (OS Error: No such file or directory, errno = 2) +This can sometimes happen if the file was deleted or moved while the tool was running. Try running "flutter clean" and try again. diff --git a/plans/audit-2026-09-23/artifacts/a14/swift.txt b/plans/audit-2026-09-23/artifacts/a14/swift.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a14/swift.txt @@ -0,0 +1 @@ + diff --git a/plans/audit-2026-09-23/issues/A14.md b/plans/audit-2026-09-23/issues/A14.md index 6b05d75c..a60ed4fe 100644 --- a/plans/audit-2026-09-23/issues/A14.md +++ b/plans/audit-2026-09-23/issues/A14.md @@ -155,3 +155,73 @@ unrelated 알림과 준비 단계 알림에 `cancelAll`을 적용하지 않는 ## 이번 조사에서 수행한 작업 읽기 조사, 공식 플랫폼 문서 확인, root와 실제 3회 문답, 이 결정 문서 작성만 수행했다. 제품 소스 편집, generator, test, native build, GitHub Issue 생성, commit은 수행하지 않았다. 실제 Android/iOS 기기 검증도 수행하지 않았다. + +## 2026-09-24 구현 준비 메모 — A13 WIP 읽기 조사 + +준비 조사 HEAD는 `204b6e65949c3cb439065dd67c64dfbb65ba1bfc`이며 A13와 U08의 미커밋 변경이 있는 시점이다. 아래는 최종 A13 commit이 아니라 그 작업 중 소스에서 확인한 내용이다. 제품 소스·공유 tracking·GitHub를 변경하거나 generator/test를 실행하지 않았으며, 실제 구현 전 A13 확정 commit을 다시 읽는다. 기존 실제 3문답과 제품 합의는 그대로 유지한다. + +### 선행 변경에서 재사용할 것 + +- `FallbackAlarmNotificationService.scheduleFallbackAlarm`은 이제 `Future`을 반환한다. 정확 권한 조회/요청 메서드도 추가됐다. A14 관측 메서드는 이 경계를 확장하고 기존 actual timing receipt를 그대로 보존한다. +- `ScheduledAlarmRecord.notificationTiming`은 exact/approximate/platformDefault를 저장하며 `ScheduledAlarmRecordModel`은 알 수 없는 timing metadata를 null로 취급한다. 관측 source를 timing이나 권한 값으로 추정하지 않는다. +- `ReconcileAlarmsUseCase`는 fresh timing/native/fallback permission과 deliveryPolicy를 stale 판정보다 먼저 계산한다. 전달 불가이면 기존 예약을 stale로 분류하고 취소 실패 ownership을 보존한다. 그러나 유지 판단에는 아직 activeProvider 일치나 OS 관측이 없다. +- `schedule_notification_status.dart`가 actual result·미래 registry ID·content·timing을 집계하는 순수 함수로 추가됐다. My Page는 활성 상태를 읽을 때 reconcile을 먼저 수행한다. A14는 이 집계에 unknown/부분 실패를 전달하고 OFF+정리 미완료도 사실대로 드러낸다. 기존 A13 안내와 UI 흐름을 재작성하지 않는다. +- A12의 cold-launch 보관/readiness 및 U08 홈 변경은 별도 선행·병행 작업이다. A14에서는 이 파일들의 동작을 되돌리지 않는다. + +### 현재 SDK와 provider 경계에서 확인한 제약 + +1. 로컬 Xcode의 `iPhoneOS26.5.sdk/System/Library/Frameworks/AlarmKit.framework/Modules/AlarmKit.swiftmodule/arm64e-apple-ios.swiftinterface:268`에서 `AlarmManager.alarms`는 `get throws`다. 같은 파일 L223–226의 Alarm은 `id: UUID`, `schedule`, `state`를 제공한다. native channel은 실패/unsupported를 empty 목록으로 바꾸지 않고 source와 함께 전달할 수 있다. 실제 native compile과 기기 조회는 아직 실행하지 않았다. +2. 기존 `AppDelegate.swift:432`의 `deterministicAlarmUUID(scheduleId)`는 비가역 hash이며 반환된 Alarm 객체에는 원래 scheduleId metadata가 없다. 따라서 registry/현재 desired 후보의 scheduleId를 native 함수에서 UUID로 매핑해 대조한다. 그 어느 후보에도 매핑되지 않는 UUID는 schedule 소유권을 역추정해 삭제하지 않는다. 기존 Q3에 따라 unknown orphan이라는 관측 한계를 남긴다. 새로운 UUID namespace로 기존 등록 ID를 바꾸지 않는다. +3. 기존 Android `MainActivity.kt:195`의 native cancel은 activity/legacy broadcast PendingIntent를 각각 취소하지만, args 없음 또는 AlarmManager 없음도 `result.success(null)`로 끝난다. 과거 native record를 처리할 때는 입력·서비스 실패를 성공으로 오인하지 않도록 해당 native 경계를 확인해야 한다. local-notification cache로 이 native cancel을 검증할 수 없다. PendingIntent 토큰 확인도 OS 전체 예약 조회로 표현하지 않는다. +4. `minimalScheduleRoutePayload`는 type과 scheduleId를 검증하며 알 수 없는 추가 필드를 최소 route로 제거한다. fallback orphan 관측은 이 검증과 실제 platform ID를 함께 사용하고, registry의 provider+ID와 충돌할 때 임의 소유권을 만들어내지 않는다. 알림 제목·본문을 새 registry/log에 저장할 필요가 없다. + +### 수정 허용 후 적용할 변경 순서 + +1. 작은 관측 모델을 domain에 추가한다. provider별 source, 조회 성공 여부, 대상별 present/absent/unknown, 최소 소유 ID를 표현한다. 최신 관측은 reconstructible 결과이며 과거 receipt만으로 다음 실행의 OS 생존을 가정하지 않는다. +2. `NotificationService`/fallback adapter에 public pending 목록 관측을 추가한다. AndroidPluginCache와 iOSNotificationCenter 출처를 명시하고 실패를 보존한다. `AlarmSchedulerService`/iOS native channel은 AlarmKit 목록을 기존 deterministic UUID와 대조한다. Android native는 새 등록을 활성화하지 않고 과거 소유 ID 취소 경계만 다룬다. +3. reconcile의 기존 A13 정책 계산 뒤 provider별 관측을 얻는다. 현재 activeProvider 불일치·내용/timing 변경·삭제·완료는 취소/교체 대상, iOS에서 유효 미래 ID 부재는 복구 대상, unknown은 확인 필요 대상으로 나눈다. Android의 현재 미래 대상은 동일 안정 ID로 재적용한다. 현재 기준시각을 지나 버린 대상을 다시 울리지 않는다. +4. provider 교체 전에 플랫폼별 취소 완료 기준을 적용한다. 실제 OS absent 또는 성공한 cancel+해당 플랫폼 후속 read-back 기준을 만족하지 못하면 tombstone과 기존 ID를 보존한다. Android fallback cache absent 단독으로 cancel 오류를 덮지 않는다. registry 쓰기와 오류 경계에서 unknown record의 소유권이 최종 replaceAll로 사라지지 않게 한다. +5. 확인된 fallback orphan을 최소 소유 정보로 정리한다. unrelated/준비 단계 알림, 매핑할 수 없는 AlarmKit UUID는 임의로 삭제하지 않는다. 새 iOS 예약도 가능한 post-schedule OS 관측을 대조하고 관측 불가를 완전 성공으로 집계하지 않는다. 미래 coverage 60개와 반복 회차 선정을 유지한다. +6. A13 순수 상태 집계에 확인 필요/부분 실패를 연결한다. OFF 상태에서 남은 취소 증거도 보이게 하고 기존 명시적 재시도 또는 정상 reconcile이 정리를 재시도할 수 있게 한다. 기술 source명은 UI에 노출하지 않는다. +7. 수정 허용 및 A13 baseline 확정 후에만 의미 있는 fake provider 모델과 method-channel/widget 회귀를 보강한다. fake 관측은 실제 fake schedule/cancel 상태를 반영해야 하며 기본값을 무조건 present로 만들어 검증을 통과시키지 않는다. provider 전환·unknown·native 오류 뒤 fallback receipt·취소 read-back·orphan ownership·반복 회차/용량·OFF 정리를 검증한다. 실제 기기 결과는 별도 pending으로 관리한다. + +전체 reconcile dirty rerun/reset 직렬화는 A15/D03 영역이다. 이번 준비 메모는 해당 경쟁 문제를 해결했다고 가정하지 않는다. + +## 2026-09-24 구현 결과 — 로컬 소스 검증 + +A13 커밋 `59d92bdd` 이후 구현했으며, 현재 기반 HEAD는 `e2fbdf0bb233ebbbb99426b9c06c0da5b4e47f9c`다. 아래 구현은 아직 이 문서 작성자의 commit·push·GitHub 갱신을 뜻하지 않는다. root가 별도 검토·커밋·원격 검증을 수행한다. 최종 소스별 SHA-256과 명령·실행 결과는 [a14-validation.json](../a14-validation.json)에 기록한다. + +### 실제로 바뀐 동작 + +- `DeliveryObservation`이 source, 성공한 목록 조회와 unknown, 최소 platform ID/scheduleId를 분리한다. Android public pending은 **plugin cache**로, iOS notification은 **Notification Center**로, native 목록은 **AlarmKit**으로 표현한다. 제목·본문을 관측 모델이나 추가 저장소에 복제하지 않는다. +- iOS native channel의 `getPendingNativeAlarms`는 실제 SDK의 throwing `AlarmManager.alarms`를 사용한다. 기존 scheduleId→UUID 함수로 후보 ID만 대조하며, 후보에 매핑할 수 없는 UUID 개수는 미확정 소유권으로 반환한다. 오류/unsupported/손상된 channel 응답은 빈 목록으로 바꾸지 않는다. +- 현재 permission으로 계산한 activeProvider와 기존 provider가 다르면 취소·교체한다. iOS의 기존 예약은 OS 목록에서 확인될 때 유지하고, 미래 대상의 OS 부재는 복구한다. 조회 unknown이면 소유권을 보존하고 성공 수에서 제외한다. +- Android는 기존 정상 재조정 흐름에서 같은 실제 소유 ID로 재적용한다. 과거 버전의 ID가 현재 `stableAlarmId` 계산값과 달라도, 내용·provider·timing이 같은 기존 요청을 갱신할 때 원래 ID를 보존하여 두 요청이 생기지 않게 한다. 알림 route가 registry의 소유 일정과 충돌하는 경우에는 기존 소유 ID를 취소한 뒤 올바른 route로 복구한다. +- 새 등록과 취소의 외부 부작용 전에 최소 ownership과 cancellationPending을 registry에 기록한다. native 등록 호출의 오류가 실제 OS 쓰기 뒤 발생했을 가능성도 고려해 정리를 확인한 뒤 fallback을 시도한다. 정리 실패이면 다른 provider를 추가하지 않는다. 실패 후 다음 실행이 정리할 ID를 잃지 않는다. +- fallback cancel은 public cancel 성공 후 해당 플랫폼의 pending read-back에서 ID 부재까지 확인한다. Android는 이를 **취소 요청 성공 및 캐시 제거 확인**으로만 해석한다. iOS native는 신뢰할 수 있는 기존 OS 부재 또는 cancel 뒤 OS 부재로 완료한다. 취소 오류/후속 조회 실패/잔존은 보류한다. +- 과거 Android native 취소는 args/AlarmManager 부재를 성공으로 처리하지 않는다. 기존 activity·legacy broadcast 토큰을 각각 취소하고 후속 토큰 부재를 확인한다. 이 역시 전체 OS 예약 목록 관측이라는 뜻이 아니며 새 Android native 등록은 여전히 금지된다. +- 검증된 fallback orphan은 실제 platform ID로만 정리한다. unrelated/준비 단계 알림과 소유권 미확정 AlarmKit UUID는 임의 삭제하지 않는다. OFF에도 native 조회 실패/미확정 UUID를 확인하며, ON·OFF 모두 일정/registry가 0개라는 이유로 관측 실패를 정상 빈 상태로 바꾸지 않는다. +- A13의 actual timing·상태 집계를 유지하면서 `꺼짐 · 알림 취소 확인 필요` / `Off · cancellation needs checking`를 추가했다. OFF에서도 My Page의 정상 재조정·resume가 미완료 정리를 다시 확인한다. 완료되면 정상 OFF로 돌아간다. +- 가까운 미래 60개, 반복 회차, 최소 route payload, A12 cold launch를 유지한다. 소유권 기록 도중 대상 시각이 지나면 실제 scheduling 호출을 하지 않아 지나간 알림을 다시 발생시키지 않는다. + +### 검증 근거 + +- focused 100개: 기존 reconcile·status·My Page 회귀와 새 provider 관측/취소 method-channel 테스트를 통과했다. +- 새 domain 회귀는 양방향 permission/provider 변경, 취소 실패→복구, native/fallback OS 유실, unknown 보존, 빈 ON/OFF의 native query 실패/미확정 UUID, Android 동일 ID 재적용과 과거 ID 보존, route 충돌, post-schedule 확인 실패, orphan·unrelated 보호, write-ahead 기록 뒤 시각 만료를 다룬다. +- public plugin method-channel 회귀는 Android와 iOS source 차이, 최소 payload 추출, 조회 실패, 취소 오류가 빈 cache로 덮이지 않는 경우, 취소 뒤 ID 잔존/조회 실패를 검사한다. native channel 회귀는 malformed/unknown 응답과 이미 없음·취소 후 없음·취소 후 조회 실패를 구별한다. +- 전체 Flutter suite는 772개 통과했다. generator는 로컬 ignored 출력으로 재생성했고 analyzer 및 generated/local-only 정책 검사를 통과했다. 이후 source freeze를 기준으로 수행한 최종 재검증과 명령별 exit는 receipt를 기준으로 한다. +- 실제 iOS 26.5 simulator SDK, Flutter framework, Objective-C registrant header로 AppDelegate·AlarmLaunchPayload·BackupExportPlugin 타입 검사를 통과했다. 이는 실제 API 타입 검사이며 Runner linking이나 OS daemon 실행 테스트가 아니다. +- Android SDK36·실제 Flutter jar·lifecycle-common을 사용해 전체 Kotlin 소스를 컴파일했다. 기존 A11의 임시 BuildConfig/R 심볼 stub을 사용했으므로 실제 resource packaging/APK 설치·실행 증거와 구분한다. 경고는 기존 deprecated API 사용이다. +- freeze 재검증 도중 Flutter test와 analyze를 동시에 실행한 한 번의 시도는 macOS ephemeral plugin symlink 생성 단계에서 실패했다. 테스트가 시작되기 전의 환경 경쟁이며 제품 테스트 실패로 분류하지 않는다. 다른 Flutter 명령을 종료한 뒤 동일 frozen 소스로 직렬 재시도했고 그 결과를 receipt에 별도로 기록한다. `flutter clean`이나 제품 소스 우회 변경은 하지 않았다. +- frozen source 최종 전체 suite는 **772/772** 통과했으며 변경 source/test 24개의 검증 전후 SHA-256이 모두 일치했다. 캡처 후에도 해시가 같음을 다시 확인했다. 원본 로그를 유지하고 `artifacts/a14/*.txt`에 LF·줄끝 공백만 정리한 사본을 저장했으며 원본/사본 hash를 receipt에 각각 기록했다. +- 새 상태의 [한국어 PNG](../artifacts/a14/cleanup-needed-ko.png)와 [영어 PNG](../artifacts/a14/cleanup-needed-en.png)를 별도로 생성해 직접 시각 확인했다. 430×932 logical px, 2배 raster, 번들 Pretendard/MaterialIcons 조건에서 새 OFF 취소 확인 문구가 잘리지 않고 보인다. 실제 MyPage widget이지만 in-memory DB와 fake partial reconcile fixture이며 기기 화면/OS 전달 증거가 아니다. 제품/test source freeze를 유지한 `/tmp` 전용 harness를 사용했고 harness 사본과 실행 로그도 보관했다. 기존 A13 캡처를 A14 새 상태의 증거로 재사용하지 않았다. + +### 선행 A13의 영속 경계 수정 + +구현 중 `_safe`가 ScheduledAlarmRecord를 재구성하면서 notificationTiming을 누락하는 것을 발견했다. 이 변경은 root가 **별도 A13 후속 커밋 `edfff97c`**로 분리했다. 실제 datasource와 SharedPreferences mock backend를 거치는 exact/approximate/platformDefault × cancellationPending true/false의 6개 추가 회귀로 raw 저장값과 content/ownership 필드 전체의 왕복 동등성을 확인했다. A14 receipt에서 이를 별도 선행 수정으로 표시하며 실제 기기 디스크 검증이라고 부르지 않는다. + +### 남아 있는 검증 및 경계 + +실제 Android/iOS 기기의 권한 철회·재허용, AlarmKit daemon 유실, 잠금화면/프로세스 종료/Doze/재부팅, 예정 시각 대비 실제 도착·중복 개수는 **미실행/device pending**이다. 테스트의 fake provider 및 mocked method channel 성공은 실제 OS 전달 성공이 아니다. UI의 최근 예약 성공과 OS pending 존재도 도착을 보장하지 않는다. + +매핑할 수 없는 AlarmKit UUID는 임의 삭제하지 않고 확인 필요로 남긴다. 권한 철회 후 다음 앱 실행 전의 공백, 취소 미확인 때문에 교체를 보류하는 공백, A15의 전역 reconcile 경쟁과 D03 reset/restore 수명주기는 이 구현이 해결하거나 보장했다고 주장하지 않는다. diff --git a/test/core/services/delivery_observation_test.dart b/test/core/services/delivery_observation_test.dart new file mode 100644 index 00000000..607ee373 --- /dev/null +++ b/test/core/services/delivery_observation_test.dart @@ -0,0 +1,273 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; +import 'package:on_time_front/core/services/notification_service.dart'; +import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/domain/entities/delivery_observation.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + const pluginChannel = MethodChannel( + 'dexterous.com/flutter/local_notifications', + ); + const nativeChannel = MethodChannel('on_time_front/native_alarm'); + final messenger = + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + final calls = []; + var pending = >[]; + var queryFails = false; + var cancelFails = false; + var cancelLeavesPending = false; + + NotificationService service({required bool ios}) { + debugDefaultTargetPlatformOverride = ios + ? TargetPlatform.iOS + : TargetPlatform.android; + if (ios) { + IOSFlutterLocalNotificationsPlugin.registerWith(); + } else { + AndroidFlutterLocalNotificationsPlugin.registerWith(); + } + return NotificationService.test( + localNotifications: FlutterLocalNotificationsPlugin(), + isIOSOverride: ios, + isAndroidOverride: !ios, + isFlutterLocalNotificationsInitialized: true, + ); + } + + setUp(() { + calls.clear(); + pending = []; + queryFails = false; + cancelFails = false; + cancelLeavesPending = false; + messenger.setMockMethodCallHandler(pluginChannel, (call) async { + calls.add(call.method); + if (call.method == 'pendingNotificationRequests') { + if (queryFails) { + throw PlatformException(code: 'read_error'); + } + return pending; + } + if (call.method == 'cancel') { + if (cancelFails) { + throw PlatformException(code: 'cancel_error'); + } + if (!cancelLeavesPending) { + pending.removeWhere( + (r) => + r['id'] == + (call.arguments is Map + ? (call.arguments as Map)['id'] + : call.arguments), + ); + } + } + return null; + }); + }); + + tearDown(() { + debugDefaultTargetPlatformOverride = null; + messenger.setMockMethodCallHandler(pluginChannel, null); + messenger.setMockMethodCallHandler(nativeChannel, null); + }); + + for (final ios in [false, true]) { + test( + '${ios ? 'iOS OS' : 'Android cache'} observation preserves source and minimal ownership', + () async { + pending = [ + { + 'id': 42, + 'title': 'PRIVATE TITLE', + 'body': 'PRIVATE BODY', + 'payload': jsonEncode({ + 'type': 'schedule_notification', + 'scheduleId': 'one', + 'untrusted': 'SECRET', + }), + }, + { + 'id': 99, + 'payload': jsonEncode({ + 'type': 'step_notification', + 'scheduleId': 'other', + }), + }, + {'id': 98, 'payload': '{broken'}, + ]; + final observed = await service( + ios: ios, + ).observePendingScheduleNotifications(); + expect(observed.available, true); + expect(observed.isOsObservation, ios); + expect( + observed.source, + ios + ? DeliveryObservationSource.iosNotificationCenter + : DeliveryObservationSource.androidPluginCache, + ); + expect(observed.entries.map((e) => e.scheduleId), ['one', null, null]); + expect(observed.presence(_record()), DeliveryPresence.present); + expect( + observed.presence(_record().copyWith(fallbackNotificationId: 123)), + DeliveryPresence.absent, + ); + }, + ); + + test( + '${ios ? 'iOS' : 'Android'} read failure is unknown, not absent', + () async { + queryFails = true; + final observed = await service( + ios: ios, + ).observePendingScheduleNotifications(); + expect(observed.presence(_record()), DeliveryPresence.unknown); + expect(observed.available, false); + }, + ); + + test( + '${ios ? 'iOS' : 'Android'} cancel needs public success and post read-back', + () async { + pending = [ + {'id': 42}, + {'id': 99}, + ]; + await service(ios: ios).cancelFallbackNotification(42); + expect(pending.map((r) => r['id']), [99]); + expect(calls, ['cancel', 'pendingNotificationRequests']); + }, + ); + + test( + '${ios ? 'iOS' : 'Android'} cache absence does not hide cancel failure', + () async { + cancelFails = true; + await expectLater( + service(ios: ios).cancelFallbackNotification(42), + throwsA(isA()), + ); + expect(calls, ['cancel']); + }, + ); + + test( + '${ios ? 'iOS' : 'Android'} remaining ID and failed post query are unconfirmed cancellations', + () async { + final notifications = service(ios: ios); + pending = [ + {'id': 42}, + ]; + cancelLeavesPending = true; + await expectLater( + notifications.cancelFallbackNotification(42), + throwsA(isA()), + ); + cancelLeavesPending = false; + queryFails = true; + await expectLater( + notifications.cancelFallbackNotification(42), + throwsA(isA()), + ); + }, + ); + } + + test( + 'AlarmKit observation rejects unavailable, malformed and unrelated IDs', + () async { + final native = AlarmSchedulerService(); + for (final response in [ + null, + { + 'source': 'iosAlarmKit', + 'scheduleIds': ['other'], + 'unmappedCount': 0, + }, + {'source': 'iosAlarmKit', 'scheduleIds': [], 'unmappedCount': '0'}, + ]) { + messenger.setMockMethodCallHandler( + nativeChannel, + (_) async => response, + ); + expect( + (await native.observePendingNativeAlarms(['one'])).available, + false, + ); + } + messenger.setMockMethodCallHandler( + nativeChannel, + (_) async => throw PlatformException(code: 'observationFailed'), + ); + expect( + (await native.observePendingNativeAlarms(['one'])).presence(_record()), + DeliveryPresence.unknown, + ); + }, + ); + + test( + 'AlarmKit already absent is idempotent; present cancel requires fresh absence', + () async { + var hasNative = false; + var failReadAfterCancel = false; + var didCancel = false; + messenger.setMockMethodCallHandler(nativeChannel, (call) async { + calls.add(call.method); + if (call.method == 'getPendingNativeAlarms') { + if (didCancel && failReadAfterCancel) { + throw PlatformException(code: 'read_error'); + } + return { + 'source': 'iosAlarmKit', + 'scheduleIds': hasNative ? ['one'] : [], + 'unmappedCount': 0, + }; + } + if (call.method == 'cancelNativeAlarm') { + didCancel = true; + hasNative = false; + } + return null; + }); + final native = AlarmSchedulerService(); + final record = _record().copyWith(provider: AlarmProvider.iosAlarmKit); + await native.cancelNativeAlarm(record); + expect(calls, ['getPendingNativeAlarms']); + calls.clear(); + hasNative = true; + await native.cancelNativeAlarm(record); + expect(calls, [ + 'getPendingNativeAlarms', + 'cancelNativeAlarm', + 'getPendingNativeAlarms', + ]); + hasNative = true; + didCancel = false; + failReadAfterCancel = true; + await expectLater( + native.cancelNativeAlarm(record), + throwsA(isA()), + ); + }, + ); +} + +ScheduledAlarmRecord _record() => ScheduledAlarmRecord( + scheduleId: 'one', + alarmTime: DateTime.utc(2030), + preparationStartTime: DateTime.utc(2030), + scheduleFingerprint: '', + provider: AlarmProvider.localNotification, + fallbackNotificationId: 42, + scheduleTitle: 'OnTime', + payload: const {}, +); diff --git a/test/core/services/scheduled_notification_delivery_test.dart b/test/core/services/scheduled_notification_delivery_test.dart index b692ad64..602c9665 100644 --- a/test/core/services/scheduled_notification_delivery_test.dart +++ b/test/core/services/scheduled_notification_delivery_test.dart @@ -139,8 +139,11 @@ void main() { 'targeted cancel uses the plugin API for pending and presented IDs, never cancelAll', () async { await service.cancelFallbackNotification(42); - expect(calls.map((call) => call.method), ['cancel']); - expect(calls.single.arguments, 42); + expect(calls.map((call) => call.method), [ + 'cancel', + 'pendingNotificationRequests', + ]); + expect(calls.first.arguments, 42); // This verifies the plugin boundary. Device notification-center read-back // remains necessary to prove that an actual displayed item was removed. }, diff --git a/test/domain/entities/schedule_notification_status_test.dart b/test/domain/entities/schedule_notification_status_test.dart index 3027b444..67bc6b08 100644 --- a/test/domain/entities/schedule_notification_status_test.dart +++ b/test/domain/entities/schedule_notification_status_test.dart @@ -60,17 +60,25 @@ void main() { ); }); - test( - 'off and denied display take precedence over stored exact registrations', - () { - final records = [_record('one', NotificationTiming.exact)]; - expect(status(records, enabled: false), ScheduleNotificationStatus.off); - expect( - status(records, canDeliver: false), - ScheduleNotificationStatus.permissionNeeded, - ); - }, - ); + test('off retains a cleanup warning and denial never becomes precise', () { + final records = [_record('one', NotificationTiming.exact)]; + expect( + status(records, enabled: false), + ScheduleNotificationStatus.cleanupNeeded, + ); + expect( + status( + [], + enabled: false, + resultStatus: AlarmReconciliationStatus.disabled, + ), + ScheduleNotificationStatus.off, + ); + expect( + status(records, canDeliver: false), + ScheduleNotificationStatus.permissionNeeded, + ); + }); test('no future registrations never becomes precise from capability', () { expect(status([]), ScheduleNotificationStatus.empty); diff --git a/test/domain/use-cases/delivery_reconciliation_test.dart b/test/domain/use-cases/delivery_reconciliation_test.dart new file mode 100644 index 00000000..230617a8 --- /dev/null +++ b/test/domain/use-cases/delivery_reconciliation_test.dart @@ -0,0 +1,336 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/domain/entities/delivery_observation.dart'; +import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; + +import 'reconcile_alarms_use_case_test.dart' + show + FakeAlarmRepository, + FakeAlarmRegistryRepository, + FakeAlarmSchedulerService, + FakeFallbackAlarmNotificationService, + scheduleWithAlarmAt; + +void main() { + final now = DateTime(2026, 5, 5, 9); + late FakeAlarmRepository schedules; + late FakeAlarmRegistryRepository registry; + late FakeAlarmSchedulerService native; + late FakeFallbackAlarmNotificationService notifications; + late ReconcileAlarmsUseCase reconcile; + + setUp(() { + schedules = FakeAlarmRepository(); + registry = FakeAlarmRegistryRepository(); + native = FakeAlarmSchedulerService() + ..capabilities = const AlarmSchedulerCapabilities( + supportsNativeAlarm: true, + nativeAlarmProvider: AlarmProvider.iosAlarmKit, + fallbackProvider: AlarmProvider.localNotification, + ); + notifications = FakeFallbackAlarmNotificationService() + ..permission = AlarmPermissionState.granted; + schedules.schedules = [ + scheduleWithAlarmAt( + id: 'one', + alarmTime: now.add(const Duration(hours: 1)), + ), + ]; + reconcile = ReconcileAlarmsUseCase.test( + schedules, + registry, + native, + notifications, + nowProvider: () => now, + languageCodeProvider: () => 'en', + timeZoneProvider: () async => 'UTC', + ); + }); + + test( + 'write-ahead identity precedes native scheduling and a now-expired target is not replayed', + () async { + var clock = now; + var sawIntent = false; + registry.onReplace = (records) { + if (records.isNotEmpty) { + expect(native.scheduledNative, isEmpty); + expect(records.single.cancellationPending, true); + sawIntent = true; + clock = now.add(const Duration(hours: 2)); + } + }; + final delayed = ReconcileAlarmsUseCase.test( + schedules, + registry, + native, + notifications, + nowProvider: () => clock, + languageCodeProvider: () => 'en', + timeZoneProvider: () async => 'UTC', + ); + final result = await delayed(); + expect(sawIntent, true); + expect(native.scheduledNative, isEmpty); + expect(notifications.scheduledFallback, isEmpty); + expect(result.armedScheduleIds, isEmpty); + expect(registry.records, isEmpty); + }, + ); + + test( + 'Android reapplication preserves an existing owned platform ID', + () async { + native.capabilities = const AlarmSchedulerCapabilities( + supportsNativeAlarm: false, + nativeAlarmProvider: AlarmProvider.none, + fallbackProvider: AlarmProvider.localNotification, + ); + notifications.timingPermission = AlarmPermissionState.granted; + await reconcile(); + registry.records = [ + registry.records.single.copyWith(fallbackNotificationId: 99), + ]; + notifications.pendingFallback + ..clear() + ..['99'] = const PendingDelivery(id: '99', scheduleId: 'one'); + expect((await reconcile()).armedScheduleIds, ['one']); + expect(notifications.pendingFallback.keys, ['99']); + expect(registry.records.single.fallbackNotificationId, 99); + expect(notifications.canceledFallback, isEmpty); + }, + ); + + test( + 'a registry-owned ID with a conflicting route is cancelled before repair', + () async { + native.nativePermission = AlarmPermissionState.denied; + await reconcile(); + final id = '${registry.records.single.fallbackNotificationId}'; + notifications.pendingFallback[id] = PendingDelivery( + id: id, + scheduleId: 'another', + ); + expect((await reconcile()).armedScheduleIds, ['one']); + expect(notifications.canceledFallback.single.scheduleId, 'one'); + expect(notifications.scheduledFallback, hasLength(2)); + expect(notifications.pendingFallback[id]!.scheduleId, 'one'); + }, + ); + + for (final enabled in [true, false]) { + for (final unknownOwnership in [false, true]) { + test( + 'empty registry with native ${unknownOwnership ? 'unmapped ID' : 'query failure'} stays incomplete enabled=$enabled', + () async { + schedules.settings = AlarmSettings(alarmsEnabled: enabled); + schedules.schedules = []; + native.nativeObservationFails = !unknownOwnership; + native.unmappedNativeCount = unknownOwnership ? 1 : 0; + final result = await reconcile(); + expect(result.status, AlarmReconciliationStatus.partial); + expect(result.armedScheduleIds, isEmpty); + expect(result.failures, isNotEmpty); + expect(native.canceledNative, isEmpty); + expect(registry.records, isEmpty); + }, + ); + } + } + + test( + 'native revoke and grant switch providers after cancelling the old registration', + () async { + await reconcile(); + expect(registry.records.single.provider, AlarmProvider.iosAlarmKit); + native.nativePermission = AlarmPermissionState.denied; + await reconcile(); + expect(native.pendingNative, isEmpty); + expect(notifications.pendingFallback, hasLength(1)); + expect(registry.records.single.provider, AlarmProvider.localNotification); + native.nativePermission = AlarmPermissionState.granted; + await reconcile(); + expect(notifications.pendingFallback, isEmpty); + expect(native.pendingNative, {'one'}); + expect(registry.records.single.provider, AlarmProvider.iosAlarmKit); + }, + ); + + test( + 'unconfirmed provider cancellation blocks duplicate fallback, then retry converges', + () async { + await reconcile(); + native.nativePermission = AlarmPermissionState.denied; + native.throwOnCancelIds.add('one'); + final failed = await reconcile(); + expect(failed.status, AlarmReconciliationStatus.partial); + expect(failed.armedScheduleIds, isEmpty); + expect(registry.records.single.cancellationPending, true); + expect(notifications.scheduledFallback, isEmpty); + native.throwOnCancelIds.clear(); + expect((await reconcile()).armedScheduleIds, ['one']); + expect(registry.records.single.provider, AlarmProvider.localNotification); + expect(registry.records.single.cancellationPending, false); + }, + ); + + test( + 'OS pending loss repairs a future native reservation but never replays an elapsed one', + () async { + await reconcile(); + native.pendingNative.clear(); + expect((await reconcile()).armedScheduleIds, ['one']); + expect(native.scheduledNative, hasLength(2)); + schedules.schedules = [ + scheduleWithAlarmAt( + id: 'one', + alarmTime: now.subtract(const Duration(minutes: 1)), + ), + ]; + await reconcile(); + expect(native.pendingNative, isEmpty); + expect(native.scheduledNative, hasLength(2)); + }, + ); + + test( + 'native query unknown retains ownership without treating absence or claiming success', + () async { + await reconcile(); + native.nativeObservationFails = true; + final result = await reconcile(); + expect(result.status, AlarmReconciliationStatus.partial); + expect(result.armedScheduleIds, isEmpty); + expect(registry.records.single.scheduleId, 'one'); + expect(native.scheduledNative, hasLength(1)); + expect(native.canceledNative, isEmpty); + }, + ); + + test( + 'iOS notification OS loss repairs while successful query allows keeping the request', + () async { + native.nativePermission = AlarmPermissionState.denied; + await reconcile(); + await reconcile(); + expect(notifications.scheduledFallback, hasLength(1)); + notifications.pendingFallback.clear(); + await reconcile(); + expect(notifications.scheduledFallback, hasLength(2)); + }, + ); + + test( + 'Android cache presence causes same-ID reapplication rather than an OS survival claim', + () async { + native.capabilities = const AlarmSchedulerCapabilities( + supportsNativeAlarm: false, + nativeAlarmProvider: AlarmProvider.none, + fallbackProvider: AlarmProvider.localNotification, + ); + notifications.timingPermission = AlarmPermissionState.granted; + await reconcile(); + await reconcile(); + expect(notifications.scheduledFallback, hasLength(2)); + expect( + notifications.scheduledFallback + .map((r) => r.fallbackNotificationId) + .toSet(), + {stableAlarmId('one')}, + ); + expect(notifications.canceledFallback, isEmpty); + expect(notifications.pendingFallback, hasLength(1)); + expect( + registry.records.single.notificationTiming, + NotificationTiming.exact, + ); + }, + ); + + test( + 'failed post-schedule verification preserves ownership but excludes armed success', + () async { + native.nativePermission = AlarmPermissionState.denied; + notifications.forgetScheduled = true; + final result = await reconcile(); + expect(result.status, AlarmReconciliationStatus.partial); + expect(result.armedScheduleIds, isEmpty); + expect(registry.records.single.scheduleId, 'one'); + expect(registry.records.single.provider, AlarmProvider.localNotification); + }, + ); + + test( + 'OS query error after startup never becomes a healthy stored notification', + () async { + native.nativePermission = AlarmPermissionState.denied; + await reconcile(); + notifications.observationFails = true; + final result = await reconcile(); + expect(result.status, AlarmReconciliationStatus.partial); + expect(result.armedScheduleIds, isEmpty); + expect(registry.records, hasLength(1)); + expect(notifications.scheduledFallback, hasLength(1)); + }, + ); + + test( + 'confirmed fallback orphans are targeted; unrelated and unknown ownership survive', + () async { + native.nativePermission = AlarmPermissionState.denied; + notifications.pendingFallback.addAll({ + '99': const PendingDelivery(id: '99', scheduleId: 'deleted'), + '100': const PendingDelivery(id: '100'), + }); + await reconcile(); + expect(notifications.canceledFallback.single.fallbackNotificationId, 99); + expect(notifications.pendingFallback.keys, contains('100')); + expect(registry.records.single.scheduleId, 'one'); + }, + ); + + test( + 'orphan cancellation failure persists its real platform ID and retries while disabled', + () async { + schedules.settings = const AlarmSettings(alarmsEnabled: false); + notifications.pendingFallback['99'] = const PendingDelivery( + id: '99', + scheduleId: 'deleted', + ); + notifications.throwOnCancelIds.add('deleted'); + expect((await reconcile()).status, AlarmReconciliationStatus.partial); + expect(registry.records.single.fallbackNotificationId, 99); + expect(registry.records.single.cancellationPending, true); + notifications.throwOnCancelIds.clear(); + expect((await reconcile()).status, AlarmReconciliationStatus.disabled); + expect(registry.records, isEmpty); + }, + ); + + test( + 'native failure uses only the actual fallback receipt after native cleanup', + () async { + native.throwOnScheduleIds.add('one'); + expect((await reconcile()).armedScheduleIds, ['one']); + expect(native.canceledNative.single.scheduleId, 'one'); + expect(registry.records.single.provider, AlarmProvider.localNotification); + expect( + registry.records.single.notificationTiming, + NotificationTiming.platformDefault, + ); + }, + ); + + test( + 'failed native write cleanup forbids fallback and keeps cancellation identity', + () async { + native.throwOnScheduleIds.add('one'); + native.throwOnCancelIds.add('one'); + final result = await reconcile(); + expect(result.armedScheduleIds, isEmpty); + expect(notifications.scheduledFallback, isEmpty); + expect(registry.records.single.provider, AlarmProvider.iosAlarmKit); + expect(registry.records.single.cancellationPending, true); + }, + ); +} diff --git a/test/domain/use-cases/reconcile_alarms_use_case_test.dart b/test/domain/use-cases/reconcile_alarms_use_case_test.dart index e637d885..b3d6c33a 100644 --- a/test/domain/use-cases/reconcile_alarms_use_case_test.dart +++ b/test/domain/use-cases/reconcile_alarms_use_case_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/delivery_observation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/data/models/scheduled_alarm_record_model.dart'; import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; @@ -57,6 +58,7 @@ class FakeAlarmRepository implements AlarmRepository { class FakeAlarmRegistryRepository implements AlarmRegistryRepository { List records = []; + void Function(List)? onReplace; @override Future> loadAll() async => List.of(records); @@ -85,6 +87,7 @@ class FakeAlarmRegistryRepository implements AlarmRegistryRepository { @override Future replaceAll(List records) async { this.records = List.of(records); + onReplace?.call(records); } } @@ -95,6 +98,23 @@ class FakeAlarmSchedulerService implements AlarmSchedulerService { ); AlarmPermissionState nativePermission = AlarmPermissionState.granted; bool throwOnCheckPermission = false; + final pendingNative = {}; + bool nativeObservationFails = false; + int unmappedNativeCount = 0; + @override + Future observePendingNativeAlarms( + Iterable ids, + ) async => nativeObservationFails + ? const DeliveryObservation.unknown( + source: DeliveryObservationSource.iosAlarmKit, + ) + : DeliveryObservation( + source: DeliveryObservationSource.iosAlarmKit, + unmappedCount: unmappedNativeCount, + entries: pendingNative + .map((id) => PendingDelivery(id: id, scheduleId: id)) + .toList(), + ); final scheduledNative = []; final canceledNative = []; final throwOnScheduleIds = {}; @@ -127,6 +147,7 @@ class FakeAlarmSchedulerService implements AlarmSchedulerService { ); } scheduledNative.add(record); + pendingNative.add(record.scheduleId); } @override @@ -135,6 +156,7 @@ class FakeAlarmSchedulerService implements AlarmSchedulerService { throw Exception('cancel failed'); } canceledNative.add(record); + pendingNative.remove(record.scheduleId); } @override @@ -169,6 +191,22 @@ class FakeFallbackAlarmNotificationService AlarmPermissionState permission = AlarmPermissionState.denied; bool throwOnCheckPermission = false; + final pendingFallback = {}; + bool observationFails = false; + bool forgetScheduled = false; + @override + Future observePending() async { + final source = timingPermission == AlarmPermissionState.unsupported + ? DeliveryObservationSource.iosNotificationCenter + : DeliveryObservationSource.androidPluginCache; + return observationFails + ? DeliveryObservation.unknown(source: source) + : DeliveryObservation( + source: source, + entries: pendingFallback.values.toList(), + ); + } + final scheduledFallback = []; final canceledFallback = []; final throwOnScheduleIds = {}; @@ -208,6 +246,14 @@ class FakeFallbackAlarmNotificationService throw Exception('fallback channel failed'); } scheduledFallback.add(record); + if (!forgetScheduled) { + final id = + '${record.fallbackNotificationId ?? stableAlarmId(record.scheduleId)}'; + pendingFallback[id] = PendingDelivery( + id: id, + scheduleId: record.scheduleId, + ); + } return actualTimingOverride ?? (timingPermission == AlarmPermissionState.unsupported ? NotificationTiming.platformDefault @@ -222,6 +268,9 @@ class FakeFallbackAlarmNotificationService throw Exception('fallback cancel failed'); } canceledFallback.add(record); + pendingFallback.remove( + '${record.fallbackNotificationId ?? stableAlarmId(record.scheduleId)}', + ); } } @@ -286,7 +335,7 @@ void main() { ); expect(fallbackService.scheduledFallback, hasLength(3)); await useCase(); - expect(fallbackService.scheduledFallback, hasLength(3)); + expect(fallbackService.scheduledFallback, hasLength(4)); }, ); @@ -823,7 +872,7 @@ void main() { expect(failed.armedScheduleIds, isEmpty); expect(registryRepository.records, isEmpty); expect(fallbackService.scheduledFallback, hasLength(1)); - expect(fallbackService.canceledFallback, hasLength(1)); + expect(fallbackService.canceledFallback, hasLength(2)); fallbackService.throwOnScheduleIds.clear(); await useCase(); expect( @@ -1047,7 +1096,7 @@ void main() { ); test( - 'keeps matching fallback registry record when fallback provider is available', + 'keeps matching iOS fallback only when OS pending confirms it', () async { schedulerService.capabilities = const AlarmSchedulerCapabilities( supportsNativeAlarm: false, @@ -1066,6 +1115,11 @@ void main() { provider: AlarmProvider.localNotification, ); registryRepository.records = [existing]; + final pendingId = '${existing.fallbackNotificationId}'; + fallbackService.pendingFallback[pendingId] = PendingDelivery( + id: pendingId, + scheduleId: existing.scheduleId, + ); alarmRepository.schedules = [schedule]; final result = await useCase(); diff --git a/test/domain/use-cases/recurring_alarm_integration_test.dart b/test/domain/use-cases/recurring_alarm_integration_test.dart index ae869104..2e5d1ecf 100644 --- a/test/domain/use-cases/recurring_alarm_integration_test.dart +++ b/test/domain/use-cases/recurring_alarm_integration_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/delivery_observation.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/core/database/database.dart'; @@ -215,6 +216,16 @@ class _UnusedUser extends Fake implements UserRepository {} class _UnusedTimers extends Fake implements TimedPreparationRepository {} class _NativeAlarms extends Fake implements AlarmSchedulerService { + final pending = {}; + @override + Future observePendingNativeAlarms( + Iterable ids, + ) async => DeliveryObservation( + source: DeliveryObservationSource.iosAlarmKit, + entries: pending + .map((id) => PendingDelivery(id: id, scheduleId: id)) + .toList(), + ); final scheduled = []; final canceled = []; @@ -232,15 +243,23 @@ class _NativeAlarms extends Fake implements AlarmSchedulerService { @override Future scheduleNativeAlarm(ScheduledAlarmRecord record) async { scheduled.add(record); + pending.add(record.scheduleId); } @override Future cancelNativeAlarm(ScheduledAlarmRecord record) async { canceled.add(record); + pending.remove(record.scheduleId); } } class _Notifications extends Fake implements FallbackAlarmNotificationService { + @override + Future observePending() async => + const DeliveryObservation( + source: DeliveryObservationSource.iosNotificationCenter, + entries: [], + ); AlarmPermissionState timingPermission = AlarmPermissionState.unsupported; int timingRequestCount = 0; diff --git a/test/presentation/alarm_allow/alarm_allow_screen_test.dart b/test/presentation/alarm_allow/alarm_allow_screen_test.dart index 43cd7f70..5f81f89a 100644 --- a/test/presentation/alarm_allow/alarm_allow_screen_test.dart +++ b/test/presentation/alarm_allow/alarm_allow_screen_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/delivery_observation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -321,6 +322,9 @@ class _FakeAlarmRegistry implements AlarmRegistryRepository { class _FakeFallbackAlarmNotificationService implements FallbackAlarmNotificationService { + @override + Future observePending() async => + const DeliveryObservation.unknown(); AlarmPermissionState timingPermission = AlarmPermissionState.unsupported; int timingRequestCount = 0; diff --git a/test/presentation/app/cubit/alarm_gate_cubit_test.dart b/test/presentation/app/cubit/alarm_gate_cubit_test.dart index dd56908a..9f89b220 100644 --- a/test/presentation/app/cubit/alarm_gate_cubit_test.dart +++ b/test/presentation/app/cubit/alarm_gate_cubit_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/delivery_observation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; import 'package:on_time_front/core/services/fallback_alarm_notification_service.dart'; @@ -368,6 +369,9 @@ class _FakeAlarmSchedulerService extends AlarmSchedulerService { class _FakeFallbackAlarmNotificationService implements FallbackAlarmNotificationService { + @override + Future observePending() async => + const DeliveryObservation.unknown(); AlarmPermissionState timingPermission = AlarmPermissionState.unsupported; int timingRequestCount = 0; diff --git a/test/presentation/my_page/my_page_screen_test.dart b/test/presentation/my_page/my_page_screen_test.dart index 0236f3c2..472bb6d0 100644 --- a/test/presentation/my_page/my_page_screen_test.dart +++ b/test/presentation/my_page/my_page_screen_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/delivery_observation.dart'; import 'package:on_time_front/core/database/local_data_operation_gate.dart'; import 'dart:async'; import 'package:shared_preferences/shared_preferences.dart'; @@ -116,10 +117,26 @@ void main() { expect(alarmRepository.updatedSettings, [false]); expect(cancelAll.callCount, 1); - expect(reconcile.callCount, 1); + expect(reconcile.callCount, 2); expect(find.text('꺼짐'), findsOneWidget); }); + testWidgets( + 'disabled delivery still shows unconfirmed cleanup and clears after recovery', + (tester) async { + alarmRepository.settings = const AlarmSettings(alarmsEnabled: false); + reconcile.disabledStatus = AlarmReconciliationStatus.partial; + await _pumpMyPage(tester); + expect(find.text('Off · cancellation needs checking'), findsOneWidget); + expect(find.text('꺼짐'), findsNothing); + reconcile.disabledStatus = AlarmReconciliationStatus.disabled; + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); + await tester.pumpAndSettle(); + expect(find.text('Off · cancellation needs checking'), findsNothing); + expect(find.text('꺼짐'), findsOneWidget); + }, + ); + testWidgets('fallback permission enables local schedule notifications', ( tester, ) async { @@ -133,7 +150,7 @@ void main() { expect(fallback.requestCount, 1); expect(alarmRepository.updatedSettings, [true]); - expect(reconcile.callCount, 1); + expect(reconcile.callCount, 2); }); testWidgets('armed local notification is reported without server status', ( @@ -177,7 +194,7 @@ void main() { await tester.tap(find.byKey(const Key('alarmSettingsSwitch'))); await tester.pumpAndSettle(); expect(alarmRepository.settings.alarmsEnabled, isTrue); - expect(reconcile.callCount, 1); + expect(reconcile.callCount, 2); expect( find.text('Improve preparation notification timing'), findsOneWidget, @@ -492,6 +509,9 @@ class _FakeAlarmSchedulerService extends AlarmSchedulerService { class _FakeFallbackAlarmNotificationService implements FallbackAlarmNotificationService { + @override + Future observePending() async => + const DeliveryObservation.unknown(); AlarmPermissionState timingPermission = AlarmPermissionState.unsupported; int timingRequestCount = 0; Completer? timingCheck; @@ -534,7 +554,8 @@ class _FakeReconcileAlarmsUseCase extends ReconcileAlarmsUseCase { AlarmRegistryRepository registryRepository, AlarmSchedulerService schedulerService, FallbackAlarmNotificationService fallbackNotificationService, - ) : super.test( + ) : repository = alarmRepository, + super.test( alarmRepository, registryRepository, schedulerService, @@ -542,6 +563,8 @@ class _FakeReconcileAlarmsUseCase extends ReconcileAlarmsUseCase { nowProvider: () => DateTime(2026), ); + final AlarmRepository repository; + AlarmReconciliationStatus disabledStatus = AlarmReconciliationStatus.disabled; int callCount = 0; List armedIds = []; AlarmReconciliationStatus status = AlarmReconciliationStatus.armed; @@ -550,7 +573,9 @@ class _FakeReconcileAlarmsUseCase extends ReconcileAlarmsUseCase { Future call() async { callCount += 1; return AlarmReconciliationResult( - status: status, + status: (await repository.getAlarmSettings()).alarmsEnabled + ? status + : disabledStatus, nativeAlarmProvider: AlarmProvider.none, fallbackProvider: AlarmProvider.localNotification, armedScheduleIds: armedIds, From b81259bf66881289bf6bca9ac7cca2548de6a326 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 01:17:59 +0900 Subject: [PATCH 26/59] docs: record delivery reconciliation evidence and implementation progress --- handoff/audit-2026-09-23-progress.md | 130 ++++++++------------------- plans/audit-2026-09-23/README.md | 6 +- plans/audit-2026-09-23/issues/A14.md | 9 +- plans/audit-2026-09-23/state.json | 20 +++-- 4 files changed, 60 insertions(+), 105 deletions(-) diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 4f3d1658..d256974b 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -1,108 +1,50 @@ -# 종합 개선 실행 상태 — 진행 중 +# OnTime 종합 개선 실행 인계 — 진행 중 -이 파일은 완료 보고가 아니라 장기 실행의 인계 기록이다. 현재 상태의 기준은 `plans/audit-2026-09-23/state.json`, 이슈 상세/실제 문답은 `plans/audit-2026-09-23/issues/`다. +현재 상태의 기준은 `plans/audit-2026-09-23/state.json`, 실제 문답과 이슈 본문은 `plans/audit-2026-09-23/issues/`다. 과거 검증 상세는 이슈별 validation JSON과 Git 기록을 읽는다. -## 사용자 요청과 실행 규칙 +## 사용자 요청과 제품 경계 -- 감사 59개 개선 항목과 5개 기능 후보 전체(64개)를 상세 GitHub 이슈로 만들고 우선순위/의존성 순서로 구현·검증한다. -- 각 이슈마다 새로운 전담 서브에이전트가 grill-with-docs를 읽고 root에게 질문을 한 번에 하나씩 묻는다. root가 사용자 대리로 답하고 실제 질문/답변 전체를 이슈에 포함한다. 가상 인터뷰 금지. -- GitHub 게시 후 원격 본문을 read-back하여 로컬 원문과 대조한다. -- Android/iOS local-only 제품. 서버/로그인/자동 cloud sync/remote analytics를 추가하지 않는다. -- 실기기 검증을 자동 테스트나 빌드 성공으로 대체하지 않는다. 미실행 수용조건이 있으면 이슈를 열어 둔다. +감사 59개 개선 항목과 기능 후보 5개, 총64개를 상세 GitHub 이슈로 만들고 우선순위/의존성 순서로 구현·검증한다. 항목마다 새 전담 서브에이전트가 grill-with-docs를 읽고 root에게 한 질문씩 보낸다. root가 사용자 대신 답하며 실제 전체 문답을 이슈에 넣고 게시 후 원격 본문과 대조한다. Android/iOS local-only 제품이며 서버·로그인·자동 cloud sync·remote analytics를 추가하지 않는다. -## 저장소 및 전달 +실행하지 않은 수용 기준이 있으면 이슈는 열어 둔다. 테스트·빌드·PR·병합·실기기 전달·store 출시는 각각 다른 상태다. 사용자 추가 승인 없이 이미 확정한 범위의 구현, 커밋, Draft PR 갱신을 이어간다. -- 저장소: DevKor-github/OnTime-front -- 브랜치: fix/audit-20260923-stabilization -- Draft PR: https://github.com/DevKor-github/OnTime-front/pull/585 (현재 task에 attach 완료) -- 감사 baseline: 44067d7ab26b6290c16cdb13b99f57387dec08f6 -- A01: #583, 6e17e764 -- A02: #584, 35f4d603 -- A03: #586, 8eab02bd -- A04: #587, c11d970c -- A05: #588, 0c41589e. 전체617 tests/analyze 및 Android release APK CI 통과. coverage84.98%. 실제기기·후속 통합 미완료 -- A11: #589, 089afee5. 원격 전체 636 tests/coverage 84.96% 및 Android APK CI 통과. Native 행동/실제 SDK 타입검사 통과. 실제 기기 검증은 대기. -- A12: #591, 실제4Q&A 및12차 검토 원격readback완료. 소스 동결, 최신 집중107 tests/analyze/정책 통과. 최신 전체suite는 ENOSPC로 원격 CI 대기. 이전707개 결과와 구분. -- A13: #592, 실제3Q&A 포함본문readback완료. A12뒤구현대기. -- A14: #593, 실제 3문답과 플랫폼 관측·취소 기준을 게시하고 본문 read-back 완료. A13 뒤 구현 대기. -- A15: #594, 실제 4문답 전체 게시 및 read-back 완료, A14 뒤 구현 대기. -- D03: #595, 실제 4문답 및 Q4 보충 포함 본문 read-back 완료. 구현 대기. A12 소스는 grill_a12만 수정하며 root는 추적 문서와 GitHub를 담당. -- 나머지 이슈는 아직 생성 전이다. 전체 완료나 64개 생성 완료로 보고하지 않는다. +## 현재 저장소와 이슈 -## 검증 경계 +- 저장소 DevKor-github/OnTime-front, 브랜치 fix/audit-20260923-stabilization, Draft PR #585 (task attach 완료). +- 감사 baseline44067d7a. upstream PR590 반복 일정 main2b02fb77을 712b3708에서 통합했다. schema2/v1 migration, backupformat2/v1 호환, 반복 소유권·frozen·exclusions 및 가까운 미래60개 계약을 보존한다. +- 상세 이슈 **19/64개** 게시 및 exact read-back 완료, **45개 생성 전**. +- A01#583, A02#584, A03#586, A04#587, A05#588, A11#589, A12#591, A13#592, A14#593, A15#594, D03#595, U08#597, A06#598, A07#599, C01#600, A08#601, A09#602, D01#603, D02#604. +- A01–A05/A11/A12/A13은 구현·자동 검증을 마쳤으나 기기 또는 후속 통합 조건이 남아 있다. U08은 홈 overflow 선행 패치만 구현했으며 전체 큰 글자 매트릭스는 미완료다. +- A14는 source24개 동결 및 root hash 대조를 완료했고 receipt·전용 UI 캡처·커밋 전달을 마무리 중이다. A15 원래 전담은 readonly 준비를 마쳤으며 A14 commit 이후 구현한다. +- A06/A07/C01/A08/A09/D01/D02/D03/A15의 실제 문답 및 상세 계획은 게시됐지만 구현 완료는 아니다. -- A02 전체 Flutter 571개 및 analyze 통과. -- A03 실제 FileSelectorIOS host 계약 등10개 + restore 화면3개 통과. -- A04 통합 전체596개 통과 후 마지막2개 회귀는 targeted18개에 포함해 추가 통과. 수정 전 동일 회귀12개에서2pass/10fail. 실제 profile partial edit와 export snapshot 경합 테스트9개 통과. analyze 통과. -- A01/A02 head 35f4d603에 대한 GitHub Dart/coverage 통과, Android release APK 및 APK/merged manifest 통과: run35825051793. identity JSON은 plans/audit-2026-09-23/a01-apk-validation.json. 일회용 검증 서명이며 store release 아님. -- 최신 PR checks는 다시 읽어야 한다. 오래된 SHA 통과를 최신 결과로 보고하지 않는다. -- Android 연결기기 없음. iOS 물리 기기는 offline. 부팅된 iPhone 17/iOS 26.5 시뮬레이터 `OnTime Recurring QA 20260923` (C8BA159D-A97A-458B-A5FC-B04A8B6F2848)에 기존 반복 QA 앱/데이터가 있으므로 보존한다. 시뮬레이터 발견만으로 picker/provider/양방향 복원/실제 알림 전달 검증을 완료하지 않는다. -- Kotlin actual SDK compile, Swift plugin/XCTest typecheck 통과는 native runtime 테스트 및 전체 iOS linking과 다르다. +## 최근 검증과 코드 기준 -## 에이전트 실행 상태 +- A12 b9f959db: remote707 tests/analyze, coverage87.11%, Android APK/manifest 통과. a12-ci-validation.json. 실제 OS cold-tap 미실행. +- U08 bde8b2de: 실제 폰트430×932 홈14px overflow 수정. focused18, analyzer 및 전용 PNG 검토. 전체 매트릭스 미완료. +- A13 59d92bdd: 정확 시각 권한·실제 timing receipt·선택적 안내 구현, 로컬734 tests. 이후 registry sanitizer가 timing을 누락하는 실제 결함을 발견해 edfff97c로 별도 수정, actual datasource/SharedPreferences mock 왕복6개 추가. +- A13 최종 후속 head e2fbdf0b: Flutter run35885445743 전체740/analyze·coverage87.20%(11319/12981), Android run35885445710 APK/manifest 통과. artifact10763011276, synthetic merge67efb127, APK SHA2560ba8caf4d01d1c043b7c237af1c67a42626bff73743d58d5a011e93c747a293a. a13-persistence-ci-validation.json에 기록. 이전734 결과는 sanitizer 결함의 검출 증거가 아니다. +- A14 최종 frozen source는 focused100, 전체772 tests/analyze, 실제 iOS26.5 SDK typecheck 및 AndroidSDK36/Flutter Kotlin compile을 통과했다. 중간 Flutter 명령 동시 실행의 ephemeral symlink 실패는 테스트 시작 전 환경 오류였고 같은 소스로 직렬 재시도해 통과했다. APK/Runner 전체 linking·실기기 전달과 구별한다. +- D02 결정으로 CONTEXT active key 용어를 정정하고 ADR0036을 e733c472에 기록했다. 정확히 한 active pair와 후보/retired의 한시 보존, manifest/store epoch와 DB runtime generation의 역할을 분리한다. 설계 확정이며 구현 완료가 아니다. -신규 agent 생성 오류는 이전 에이전트 완료 후 해소됐다. 원래 요청한 항목별 새 전담 agent 방식을 유지한다. A12 구현 중, A13/A14는 각 원래 전담 agent가 이후 구현하며 D03 문답과 게시가 완료됐다. U08 신규 agent 생성은 일시 한도 오류로 대기하며 A12 완료 후 다시 시도한다. 이전의 새 작업 또는 agent 재사용 질문은 더 이상 진행 조건이 아니다. +## 에이전트와 다음 작업 -## 로컬 환경 +1. A14 전담 `/root/grill_a14`의 최종 receipt/capture를 읽고 root가 #593 본문 exact read-back→명시적 source staging→commit/push→새 SHA CI를 기록한다. A14 동결 소스를 임의 수정하지 않는다. +2. `/root/grill_a15` 원래 전담에 A14 commit SHA를 전달하고 확정 #594 범위를 구현하도록 한다. 작은 단일 알림 writer, request cutoff별 완료와 trailing rerun, replacement quiesce/late native 반환 소유권을 지킨다. D03의 durable crash journal이나 D02 manifest를 여기서 완료했다고 주장하지 않는다. +3. 이후 D03→A06→A07→C01→A08→A09 순서와 README 전체 우선순위를 따른다. 독립적인 다음 grill은 병행 가능하다. +4. D04 신규 전담 생성은 agent thread limit으로 두 번 거절됐다. 다른 이슈 전담을 재사용하지 말고 슬롯/다음 실행에서 다시 생성한다. 기존 A15 전담 followup은 정상 접수됐다. 사용자 추가 승인/새 작업 요청은 필요 없다. +5. D04 사전 read-only 확인: upstream schema2와 실제 v1→v2 onUpgrade가 존재한다. recurring_migration_test는 현재v2 파일에서 반복 표면을 제거해 v1처럼 만든 파일을 재연다. 따라서 감사 baseline의 'migration 없음'을 그대로 재게시하지 말고 고정 과거 schema fixture·upgrade 실패 rollback/키 보존 등 실제 남은 범위를 새 전담 grill로 재평가한다. -- Flutter: /Users/ejunpark/Library/flutter/bin/flutter -- Dart: /Users/ejunpark/Library/flutter/bin/cache/dart-sdk/bin/dart -- gh: /Users/ejunpark/.local/bin/gh -- shell 실행은 login:false. login shell에서 무관한 rbenv/디스크 오류가 있었음. -- 현재 worktree `.dart_tool`/codegen 준비됨. source 변경 후 generator 재실행, 생성 Dart는 ignored artifact로 commit 금지. -- 디스크 여유 최근 약0.3GiB로 줄었으며 수시로 변함. 무거운 iOS/Android local build 전에 확인하고 사용자 파일을 삭제해 공간을 만들지 않는다. -- baseline npm test는 의도적 실패 placeholder이므로 쓰지 않는다. +## 환경과 주의 -## 다음 작업 +- Flutter `/Users/ejunpark/Library/flutter/bin/flutter`, Dart `/Users/ejunpark/Library/flutter/bin/cache/dart-sdk/bin/dart`, gh `/Users/ejunpark/.local/bin/gh`. +- shell은 login:false, sandbox_permissions를 넣지 않는다. Flutter 명령들은 ephemeral plugin 준비 경합을 피하도록 직렬 실행한다. +- generated Dart `*.g.dart`/config/mocks/freezed는 ignored local outputs이며 커밋하지 않는다. 기존 tracked l10n 출력은 변경 ARB와 함께 관리한다. root npm test는 의도적 실패 placeholder이므로 사용하지 않는다. +- 디스크 최근6.6GiB. 사용자 파일/다른 worktree를 정리하지 않는다. Android 연결기기 없고 물리iOS는 offline이었다. +- 부팅된 iPhone17/iOS26.5 simulator OnTime Recurring QA 20260923 (C8BA159D-A97A-458B-A5FC-B04A8B6F2848)의 upstream QA 앱·데이터는 보존한다. 발견만으로 실제 전달·provider·양방향복원 검증을 통과 처리하지 않는다. +- `/Users/ejunpark/.codex/worktrees/7b29/OnTime-front`는 다른 작업이다. 수정하지 않는다. +- helper `/tmp/ontime-ci-audit.py`는 전체40자리 head SHA를 요구하며 해당 head의 Flutter와 Android 결과/작은 artifact identity를 기록한다. short SHA의 빈 결과를 근거로 쓰지 않는다. -1. A12 전담 구현과 실제 router/Bloc/gate 회귀를 검토하고 source freeze 후 전체 테스트·커밋·CI를 진행한다. -2. U08 새 전담 grill을 생성해 홈 카드 overflow 근거를 이슈화한다. 오류를 숨기지 않고 430×932 홈 회귀를 좁게 선행 수정할 수 있도록 의존성을 정한다. A12 완료 뒤 A13, A14, A15 순서로 구현한다. -3. A12 커밋의 새 CI 결과와 APK identity를 기록하고 PR 및 이슈 검증 상태를 갱신한다. 통합 712b3708 CI는 모두 통과했다. -4. 기존 QA 데이터와 사용자 파일을 보존하며 native 검증 환경을 활용한다. 실행하지 못한 실제 기기 조건은 open 상태로 유지한다. +## A14 전달 완료와 A15 시작 -## Upstream main 통합과 CI - -A11 커밋 `089afee51b2572339b4650bd3679b1a1ef72781e`의 원격 workflow_dispatch Flutter run 35828807338은 636 tests/coverage 84.96%, Android run 35828809625는 APK/manifest 통과다. 각 receipt를 plans에 기록했다. - -작업 중 main에 PR #590 반복 일정 기능이 병합되어 PR #585가 충돌했다. PR baseRefOid/REST base.sha는 과거 기준을 반환했지만 git ls-remote 및 git/ref/heads/main은 `2b02fb770324de291488f3c74fe503dc063e2459`를 반환했다. 현재 main을 확인할 때 PR base 필드에 의존하지 않는다. - -Root는 main을 통합하면서 backup_service_test.dart 충돌 하나를 양측 회귀 보존으로 해결했다. 반복 일정, DB schema2 및 v1 migration, backup format2/v1 호환과 기존 감사 수정을 함께 검증했다. 생성기, local analyze, 전체 678 tests, generated/local-only 정책 검사가 통과했다. 통합 커밋 `712b37081f95ea30aa6d1c61072d167ddaec78de`를 push했고 PR은 MERGEABLE이다. 원격 Flutter run 35829634205도 678 tests와 coverage 85.35% (10679/12512)를 통과했다. Android run 35829634224도 통과했고, PR synthetic merge 31158b42의 APK identity를 upstream-integration-apk-validation.json에 기록했다. - -A12는 통합 이후 구현을 재개했다. A13/A14/A15 문답 준비는 독립적으로 진행하되 소스 구현은 순차적이다. 후속 감사(D04 migration, F01 반복 일정 등)는 upstream 기능으로 해결된 범위를 재평가해야 한다. 감사 baseline은 44067d7a이며, 현재 통합한 main은 2b02fb77이다. - -## 추가 UI 회귀 발견 - -A12 실제 router 테스트에서 EN, 430×932/DPR1/text scale1, 실제 Pretendard 폰트로 home_screen_tmp.dart:185가 14px overflow했다. 원본 로그는 /tmp/a12-overflow.log, 영속 사본은 plans/audit-2026-09-23/u08-home-overflow-baseline.txt다. 390×844의 A12 경로는 통과한다. 홈 소스는 아직 수정하지 않았고 U08 전체 완료를 의미하지 않는다. 신규 grill_u08 생성이 2회 한도 오류로 실패했으며 A12 완료 뒤 다시 시도한다. 사용자에게 새 질문을 할 상황이 아니며 이미 준비된 A13 구현 등 진행 가능한 작업을 계속한다. - -A12 최종 소스24개 SHA-256을 a12-validation.json과 대조해 일치 확인했다. 선택한 준비 C→D→홈 실제 router 경로를 검증했다. 신규 U08 agent 생성 및 기존 A13 followup 모두 agent thread limit 오류였으며 조건 완화 없이 재시도/기존 확정 이슈 구현을 진행한다. - -## 2026-09-24 이어서 실행 - -A12 b9f959db commit/push 및 PR 원격 head 확인 완료. GitHub Flutter run35881988783, Android run35881988613 실행 중. PR 상세 본문 갱신·동일성 read-back 완료. Agent 한도 해소 후 신규 U08/A06 전담 grill과 기존 A13 전담 구현이 실행 중이며 root가 실제 질문에 순차 답변한다. A12 이전707개 결과는 최종 소스 검증으로 재사용하지 않는다. - -A12 최종 b9f959db Flutter run35881988783이 707 tests/analyze/coverage87.11% (11189/12844) 통과했다. 이전 로컬707과 수는 같지만 최종 source에 대한 별도의 원격 증거다. Android run35881988613은 계속 실행 중. - -U08 #597와 A06 #598 생성 후 exact body read-back 완료. 13/64개 이슈 게시, 51개 생성 전. U08 홈 선행패치 구현 중(전체 매트릭스 완료 아님). A06 문답3개 및 OFF정책 정정 포함 준비 완료, A07 새 전담 grill 시작. - -A12 Android run35881988613 통과, artifact10760609601 및 synthetic merge1c8e60c3 identity read-back 완료. 상태 code_verified_device_pending. #591 본문에 최종 Flutter707/87.11%·APK 기록 추가 후 exact read-back 완료. - -A07 #599 상세본문/실제3문답과추가정정 게시후 exactreadback완료. 전체14/64개게시, 50개미생성. A14는최신A13소스readonly구현준비중이며A13commit전소스수정금지. - -U08 홈선행패치 sourcefreeze, 18 tests/analyze 및 root PNG시각검토/모든hash 일치. #597 본문업데이트 exactreadback완료. 전체U08미완료 유지. C01 신규전담grill시작. - -A13 `59d92bdd03cdd5e2dc33e95dd844f8d0bb508eab` 구현커밋. 최종전체734/analyze/정책통과; #592본체증거링크를정확commit영구링크로갱신후exactreadback완료. U08선행홈commit bde8b2de도포함. A14전담구현허용, C01/A08새전담grill진행. 원격push/CI확인이어가기. - -C01 #600 exactreadback완료: 15/64게시,49미생성. A08/A09전담grill진행중. A13/U08 push원격head dac9c5a2, Flutter35884708345/Android35884708227 실행중. A14조사에서 _safe가notificationTiming필드를drop하는A13실제persistence gap발견, 별도A13fix와realSharedPreferences왕복회귀우선처리. 기존734통과가이gap을커버하지못했음을명시. - -A13 persistence후속 `edfff97c4806fb2d154c324edf8965b1feb218e0`: sanitizer필드보존+actualdatasource/SharedPreferences mock 왕복10개통과, 원격#592본문갱신exactreadback완료. A08 #601 게시exactreadback완료, 전체16/64게시. A09문서작성중,D01새전담grill시작. - - -## 최신 진행: A13 후속 CI / A09·D01 게시 - -- A09 #602와 D01 #603을 상세 실제 문답과 함께 게시하고 exact body read-back했다. 총 18/64개 게시, 46개 미생성. D02는 네 문답 확정 후 상세 문서 작성 중이다. -- A13 registry 후속 수정 edfff97c가 포함된 e2fbdf0b의 원격 Flutter740 tests/analyze·coverage87.20%, Android APK/manifest가 모두 통과했다. a13-persistence-ci-validation.json에 artifact10763011276 및 synthetic merge67efb127 identity를 기록했다. #592 최종 본문도 원격 동일성을 검증했다. -- A14 전담이 최종 소스/전체 테스트/native SDK 검증 중이다. root는 unknown/unmapped 관측이 빈 ON/OFF에서 성공으로 사라지지 않게 리뷰했고 회귀가 추가됐다. 실제 OS 전달은 미실행이다. -- D04 새 전담 생성은 agent thread limit으로 실패했다. 기존 전담을 재사용해 새 이슈 문답을 대신하지 않고 이후 재시도한다. 사용자 추가 승인 사항은 없다. -- CONTEXT의 Installation Data Key는 정확히 하나의 active key라는 뜻으로 D02 문답에 맞춰 정정했다. 비활성 후보/retired 재료는 안전한 복구 정리까지 한시 보존한다. - -D02 #604도 실제 네 문답 및 보충 전체를 게시하고 exact body read-back했다. 현재 19/64개 게시, 45개 미생성. ADR0036은 e733c472에 기록했고 이슈에서 해당 commit permalink로 연결했다. A15 원래 전담 followup은 정상 접수되어 A14 commit 전 readonly 준비 중이다. D04 새 전담 생성 재시도는 여전히 thread limit이었다. +A14는 282db21f5effa86eb2f82c81c247d57a0a6acfd0로 커밋했다. source24 해시 및 최종100/772/analyze/native SDK receipt와 전용 KO/EN PNG를 포함한다. #593 본문에 최종 구현/증거 링크를 넣고 exact read-back했다. A15 원래 전담에게 해당 SHA를 전달하고 구현을 허용했다. A14 원격 CI는 push 이후 새 head로 확인하며 아직 통과를 주장하지 않는다. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 7c4cdaaf..c4686cae 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -24,8 +24,8 @@ | A11 | [#589](https://github.com/DevKor-github/OnTime-front/issues/589) | 089afee5 원격636 tests/84.96%·APK 통과, 최신main 통합678 tests 통과, 기기 검증 대기 | | A12 | [#591](https://github.com/DevKor-github/OnTime-front/issues/591) | 콜드 탭·선택 준비 유지 구현, 최종 b9f959db 원격707 tests/analyze·coverage87.11%·APK 통과, 기기 검증 대기 | | A13 | [#592](https://github.com/DevKor-github/OnTime-front/issues/592) | 예약/안내·registry 후속 수정 원격740 tests/87.20%·APK 통과, 기기 대기 | -| A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | 실제3문답·플랫폼별 관측/취소 기준 확정, 전담 구현 중 | -| A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 실제4문답·요청별 완료/실패 및 세대 직렬화 기준 확정, A14 뒤 구현 | +| A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측·취소 보존 구현282db21f, 로컬772 tests/native SDK 통과, 원격 CI·기기 대기 | +| A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 실제4문답·요청별 완료/실패 및 세대 직렬화 기준 확정, 전담 구현 중 | | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 실제4문답·취소 journal/중단 복구/부분 결과 기준 확정, A15 뒤 구현 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | 실제3문답·정정 포함, 신선한 실제 타이머 전환 알림 기준 확정 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | 실제3문답·추가 정정 포함, DB 원자적 시작·run 시각·실패 재시도 기준 확정 | @@ -36,7 +36,7 @@ | D02 | [#604](https://github.com/DevKor-github/OnTime-front/issues/604) | 실제4문답·손상 DB 후보 검증/활성 쌍 교체·중단 복구 기준 확정 | | U08 | [#597](https://github.com/DevKor-github/OnTime-front/issues/597) | 홈 overflow 선행 수정18 tests/analyze 통과, 전체 매트릭스·기기 검증 미완료 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **19/64개**를 게시하고 원격 본문 동일성을 확인했으며 **45개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14 최종 검증을 진행 중이며 A15 전담은 순차 구현을 준비한다. D02까지 실제 문답과 복구 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **19/64개**를 게시하고 원격 본문 동일성을 확인했으며 **45개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14 로컬772 tests·native SDK 검증 후282db21f로 커밋했고 A15 전담 구현을 시작했다. D02까지 실제 문답과 복구 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/A14.md b/plans/audit-2026-09-23/issues/A14.md index a60ed4fe..35459bb6 100644 --- a/plans/audit-2026-09-23/issues/A14.md +++ b/plans/audit-2026-09-23/issues/A14.md @@ -189,7 +189,7 @@ unrelated 알림과 준비 단계 알림에 `cancelAll`을 적용하지 않는 ## 2026-09-24 구현 결과 — 로컬 소스 검증 -A13 커밋 `59d92bdd` 이후 구현했으며, 현재 기반 HEAD는 `e2fbdf0bb233ebbbb99426b9c06c0da5b4e47f9c`다. 아래 구현은 아직 이 문서 작성자의 commit·push·GitHub 갱신을 뜻하지 않는다. root가 별도 검토·커밋·원격 검증을 수행한다. 최종 소스별 SHA-256과 명령·실행 결과는 [a14-validation.json](../a14-validation.json)에 기록한다. +A13 커밋 `59d92bdd` 이후 구현했으며, 현재 기반 HEAD는 `e2fbdf0bb233ebbbb99426b9c06c0da5b4e47f9c`다. 아래 구현은 아직 이 문서 작성자의 commit·push·GitHub 갱신을 뜻하지 않는다. root가 별도 검토·커밋·원격 검증을 수행한다. 최종 소스별 SHA-256과 명령·실행 결과는 [a14-validation.json](https://github.com/DevKor-github/OnTime-front/blob/282db21f5effa86eb2f82c81c247d57a0a6acfd0/plans/audit-2026-09-23/a14-validation.json)에 기록한다. ### 실제로 바뀐 동작 @@ -225,3 +225,10 @@ A13 커밋 `59d92bdd` 이후 구현했으며, 현재 기반 HEAD는 `e2fbdf0bb23 실제 Android/iOS 기기의 권한 철회·재허용, AlarmKit daemon 유실, 잠금화면/프로세스 종료/Doze/재부팅, 예정 시각 대비 실제 도착·중복 개수는 **미실행/device pending**이다. 테스트의 fake provider 및 mocked method channel 성공은 실제 OS 전달 성공이 아니다. UI의 최근 예약 성공과 OS pending 존재도 도착을 보장하지 않는다. 매핑할 수 없는 AlarmKit UUID는 임의 삭제하지 않고 확인 필요로 남긴다. 권한 철회 후 다음 앱 실행 전의 공백, 취소 미확인 때문에 교체를 보류하는 공백, A15의 전역 reconcile 경쟁과 D03 reset/restore 수명주기는 이 구현이 해결하거나 보장했다고 주장하지 않는다. + + +## Root 전달 검증 + +구현 커밋은 `282db21f5effa86eb2f82c81c247d57a0a6acfd0`다. Root가 source24개 SHA256을 독립 대조했고 최종 full772·analyze 로그를 확인했다. 새 KO/EN My Page 정리 확인 상태 PNG 두 장을 직접 열어 새 문구에 잘림이 없음을 확인했다. 기존 EN 화면의 한국어 혼재는 U05 후속 범위이며 A14 전체 번역 완료를 뜻하지 않는다. 원격 CI는 이 커밋을 push한 뒤 해당 head 기준으로 확인한다. + +[KO 상태 캡처](https://github.com/DevKor-github/OnTime-front/blob/282db21f5effa86eb2f82c81c247d57a0a6acfd0/plans/audit-2026-09-23/artifacts/a14/cleanup-needed-ko.png) · [EN 상태 캡처](https://github.com/DevKor-github/OnTime-front/blob/282db21f5effa86eb2f82c81c247d57a0a6acfd0/plans/audit-2026-09-23/artifacts/a14/cleanup-needed-en.png) diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 4b02d944..20fee29c 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -290,18 +290,24 @@ "`reconcile_alarms_use_case.dart:204-230,529-538`; `my_page_screen.dart:203-214`", "permission grant/revoke·OS 예약 유실·재시작 후 provider와 OS pending/registry를 일치시킴. 불일치 시 취소·교체·정직한 상태 표시." ], - "status": "implementation_in_progress", + "status": "code_verified_ci_pending", "agent": "/root/grill_a14", "issue_number": 593, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/593", "decisions_file": "plans/audit-2026-09-23/issues/A14.md", - "implementation_commit": null, - "pr_url": null, - "verification": [], - "blockers": [], + "implementation_commit": "282db21f5effa86eb2f82c81c247d57a0a6acfd0", + "pr_url": "https://github.com/DevKor-github/OnTime-front/pull/585", + "verification": [ + "Frozen source24 SHA256 matched; focused100/full772, analyzer, native Swift SDK/Kotlin compile, generated/local-only checks passed. a14-validation.json.", + "Root visually reviewed actual MyPage standalone KO/EN captures; fake receipts/in-memory DB, not physical OS screenshots." + ], + "blockers": [ + "Remote CI pending; actual Android/iOS delivery and permission/Doze/reboot matrix not executed." + ], "sequence": 9, "issue_body_sha256": "23769c09503d629f813df5b93db7b2b6fce715dd15ea1571ea09fa06b9c1a5d4", - "readback_verified": true + "readback_verified": true, + "body_sha256": "2c068b0a81fa6eae7e339bff394a6e492a71f8395cc37ad7a724a2b7652c85a8" }, { "audit_id": "A15", @@ -314,7 +320,7 @@ "`reconcile_alarms_use_case.dart:43-57`; `schedule_mutation_alarm_effects_coordinator.dart:37-52`", "dirty flag/data revision 기반 재실행 또는 직렬 작업 큐. snapshot 읽기 뒤 생성·수정·삭제를 끼워 넣어 최종 OS 예약이 최신 DB를 반영하는지 검사. reset/cancel과 경계 공유." ], - "status": "ready", + "status": "implementing", "agent": "/root/grill_a15", "issue_number": 594, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/594", From bebf2a31a3a27fab93603492c0b15d452cd575ed Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 01:29:24 +0900 Subject: [PATCH 27/59] docs: record migration safeguards and delivery CI evidence --- handoff/audit-2026-09-23-progress.md | 14 +- plans/audit-2026-09-23/README.md | 7 +- plans/audit-2026-09-23/a14-ci-validation.json | 42 ++++ plans/audit-2026-09-23/issues/A14.md | 7 + plans/audit-2026-09-23/issues/D04.md | 219 ++++++++++++++++++ plans/audit-2026-09-23/state.json | 24 +- 6 files changed, 298 insertions(+), 15 deletions(-) create mode 100644 plans/audit-2026-09-23/a14-ci-validation.json create mode 100644 plans/audit-2026-09-23/issues/D04.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index d256974b..a5db3ee1 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -12,8 +12,8 @@ - 저장소 DevKor-github/OnTime-front, 브랜치 fix/audit-20260923-stabilization, Draft PR #585 (task attach 완료). - 감사 baseline44067d7a. upstream PR590 반복 일정 main2b02fb77을 712b3708에서 통합했다. schema2/v1 migration, backupformat2/v1 호환, 반복 소유권·frozen·exclusions 및 가까운 미래60개 계약을 보존한다. -- 상세 이슈 **19/64개** 게시 및 exact read-back 완료, **45개 생성 전**. -- A01#583, A02#584, A03#586, A04#587, A05#588, A11#589, A12#591, A13#592, A14#593, A15#594, D03#595, U08#597, A06#598, A07#599, C01#600, A08#601, A09#602, D01#603, D02#604. +- 상세 이슈 **20/64개** 게시 및 exact read-back 완료, **44개 생성 전**. +- A01#583, A02#584, A03#586, A04#587, A05#588, A11#589, A12#591, A13#592, A14#593, A15#594, D03#595, U08#597, A06#598, A07#599, C01#600, A08#601, A09#602, D01#603, D02#604, D04#605. - A01–A05/A11/A12/A13은 구현·자동 검증을 마쳤으나 기기 또는 후속 통합 조건이 남아 있다. U08은 홈 overflow 선행 패치만 구현했으며 전체 큰 글자 매트릭스는 미완료다. - A14는 source24개 동결 및 root hash 대조를 완료했고 receipt·전용 UI 캡처·커밋 전달을 마무리 중이다. A15 원래 전담은 readonly 준비를 마쳤으며 A14 commit 이후 구현한다. - A06/A07/C01/A08/A09/D01/D02/D03/A15의 실제 문답 및 상세 계획은 게시됐지만 구현 완료는 아니다. @@ -48,3 +48,13 @@ ## A14 전달 완료와 A15 시작 A14는 282db21f5effa86eb2f82c81c247d57a0a6acfd0로 커밋했다. source24 해시 및 최종100/772/analyze/native SDK receipt와 전용 KO/EN PNG를 포함한다. #593 본문에 최종 구현/증거 링크를 넣고 exact read-back했다. A15 원래 전담에게 해당 SHA를 전달하고 구현을 허용했다. A14 원격 CI는 push 이후 새 head로 확인하며 아직 통과를 주장하지 않는다. + +원격 push head는 b81259bf66881289bf6bca9ac7cca2548de6a326이며 PR #585 본문을 A14 구현/증거 및 총19개 이슈에 맞춰 갱신하고 exact read-back했다. Flutter run35887794993, Android run35887794956은 실행 중이다. a14-ci-validation.json은 현재 중간 snapshot이며 최종 통과로 보고하지 않는다. 다음 실행은 이 두 run을 같은 전체 SHA로 확인한다. A15는 snapshot 이후 새 일정+두 번째 call barrier가 수정 전 실패함을 /tmp/a15-red.log로 확인하고 구현 중이다. + +## D04 게시 / A14 최종 CI / A15 리뷰 + +D04 #605 실제 네 문답 및 보충을 게시하고 exact read-back했다. 현재20/64,44개 미생성. 실제v1→v2가 있으므로 baseline을 정정했고 DDL/version 원자성, 미래 schema downgrade 거부, 과거 독립 fixture, Android background SQLCipher loader와 cipher_version guard, 실제 모바일 검증을 AC로 확정했다. 앱에서 평문 유출을 재현했다는 뜻은 아니다. + +A14 b81259bf의 Flutter35887794993 전체772/analyze·coverage87.44% 및 Android35887794956 APK/manifest가 모두 통과했다. artifact10763053548/synthetic merge3ebcf192/APK7e88d688...를 receipt에 기록했다. 다만 A15 리뷰에서 실제 registry의 scheduleId dedup이 복수 provider/platform-ID 취소 ownership을 잃을 수 있음을 발견해 후속 통합 미완료를 #593에 명시했다. A15가 실제 repo/datasource 왕복, 중복 ownership keeper·취소, reset intent 이후 실패 시 신규 writer 차단, DB 시작/완료 후 취소 오류의 별도 처리까지 수정 중이다. 이 검토 후 전체 A15 테스트는 아직 완료되지 않았다. + +D05 새 전담 생성은 이번 continuation에서도 thread limit으로 한 번 실패했다. D04 신규 생성은 성공했다. 다음 continuation에서 D05 신규 전담을 다시 생성하며 기존 전담을 다른 항목에 재사용하지 않는다. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index c4686cae..0e5a9f4a 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -24,7 +24,7 @@ | A11 | [#589](https://github.com/DevKor-github/OnTime-front/issues/589) | 089afee5 원격636 tests/84.96%·APK 통과, 최신main 통합678 tests 통과, 기기 검증 대기 | | A12 | [#591](https://github.com/DevKor-github/OnTime-front/issues/591) | 콜드 탭·선택 준비 유지 구현, 최종 b9f959db 원격707 tests/analyze·coverage87.11%·APK 통과, 기기 검증 대기 | | A13 | [#592](https://github.com/DevKor-github/OnTime-front/issues/592) | 예약/안내·registry 후속 수정 원격740 tests/87.20%·APK 통과, 기기 대기 | -| A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측·취소 보존 구현282db21f, 로컬772 tests/native SDK 통과, 원격 CI·기기 대기 | +| A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측 구현282db21f, 원격772 tests/87.44%·APK 통과, A15 실제 ownership 저장 후속·기기 대기 | | A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 실제4문답·요청별 완료/실패 및 세대 직렬화 기준 확정, 전담 구현 중 | | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 실제4문답·취소 journal/중단 복구/부분 결과 기준 확정, A15 뒤 구현 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | 실제3문답·정정 포함, 신선한 실제 타이머 전환 알림 기준 확정 | @@ -34,9 +34,10 @@ | A09 | [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | 실제4문답·staging/runtime 세대·복원 후 정리 기준 확정 | | D01 | [#603](https://github.com/DevKor-github/OnTime-front/issues/603) | 실제4문답·DB 비의존 시작 복구·키/파일 보존 기준 확정 | | D02 | [#604](https://github.com/DevKor-github/OnTime-front/issues/604) | 실제4문답·손상 DB 후보 검증/활성 쌍 교체·중단 복구 기준 확정 | +| D04 | [#605](https://github.com/DevKor-github/OnTime-front/issues/605) | 실제4문답·migration 원자성/역사 schema/SQLCipher 확인·실패 보존 기준 확정 | | U08 | [#597](https://github.com/DevKor-github/OnTime-front/issues/597) | 홈 overflow 선행 수정18 tests/analyze 통과, 전체 매트릭스·기기 검증 미완료 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **19/64개**를 게시하고 원격 본문 동일성을 확인했으며 **45개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14 로컬772 tests·native SDK 검증 후282db21f로 커밋했고 A15 전담 구현을 시작했다. D02까지 실제 문답과 복구 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **20/64개**를 게시하고 원격 본문 동일성을 확인했으며 **44개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14 로컬772 tests·native SDK 검증 후282db21f로 커밋했고 A15 전담 구현을 시작했다. D02까지 실제 문답과 복구 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 @@ -60,7 +61,7 @@ | 16 | A09 | P1 | 복원은 DB만 교체하고 기존 timed preparation/early start SharedPreferences와 메모리 세션을 비우지 않는다. 동일 ID·fingerprint를 복원하면 백업에서 제외되어야 할 이전 진행 상태를 재사용할 수 있다. | | 17 | D01 | P1 | bootstrap 실패가 runApp 이전에 나면 복구 화면에도 진입하지 못한다. secure storage·파일 정리 실패를 제한된 복구 상태로 전달해야 한다. | | 18 | D02 | P1 | 복구 화면은 재시도와 전체 삭제만 제공한다. ADR가 약속한 백업 복원 경로가 없으며 정상 DB를 요구하는 현재 restore로는 손상 DB 복구도 해결되지 않는다. reset 예외도 화면에서 처리하지 않는다. | -| 19 | D04 | P1 | DB v1의 onUpgrade는 명시적으로 실패한다. 현재 v1 자체의 장애라고 볼 수 없으나 다음 schema 변경 전 migration 기반이 필요하다. | +| 19 | D04 | P1 | 현재 v1→v2는 존재한다. DDL/version 원자성, 미래 버전 거부, 역사 fixture, SQLCipher loader/guard 및 실패 보존을 보강한다. | | 20 | D05 | P1 | 복원 전 전체 파일을 readAsBytes하고 복호화 결과·JSON도 모두 메모리에 올린다. frame 수 상한은 있지만 총 파일 크기·총 레코드/문자열의 제품 한도가 없다. timezone ID는 비어 있지 않은지만 확인한다. | | 21 | A10 | P1 | 절대시각과 표시용 civil time 사용이 혼재한다. 준비 시작은 occurrenceInstantUtc를 쓰지만 화면 카운트다운·지난 일정 판정 일부는 scheduleTime을 직접 쓴다. 다른 시간대 이동 시 남은 시간/진행 대상이 달라질 수 있다. | | 22 | U02 | P1 | 일정 시간대 선택과 다른 기기 시간대에서의 환산 시각을 표시한다. | diff --git a/plans/audit-2026-09-23/a14-ci-validation.json b/plans/audit-2026-09-23/a14-ci-validation.json new file mode 100644 index 00000000..61ee75c3 --- /dev/null +++ b/plans/audit-2026-09-23/a14-ci-validation.json @@ -0,0 +1,42 @@ +{ + "head_commit": "b81259bf66881289bf6bca9ac7cca2548de6a326", + "runs": [ + { + "conclusion": "success", + "databaseId": 35887794993, + "event": "pull_request", + "headSha": "b81259bf66881289bf6bca9ac7cca2548de6a326", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35887794993", + "workflowName": "Flutter Testing", + "tests_passed": 772, + "coverage": { + "percent": 87.44, + "lines_hit": 11487, + "lines_found": 13137 + } + }, + { + "conclusion": "success", + "databaseId": 35887794956, + "event": "pull_request", + "headSha": "b81259bf66881289bf6bca9ac7cca2548de6a326", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35887794956", + "workflowName": "Android Notification Contract", + "artifact_id": 10763053548, + "identity": { + "commit": "3ebcf19237dcdd87e262c176762149dfd7ef0e8b", + "artifact": "build/app/outputs/flutter-apk/app-release.apk", + "sha256": "7e88d6882e3d1060965b33e7d6bab12e04f5aff3a4be5448e1ec859f8c314b60", + "signing": "disposable CI validation key; not a store release", + "merged_manifests": [ + "build/app/intermediates/merged_manifest/release/outputReleaseAppLinkSettings/AndroidManifest.xml", + "build/app/intermediates/merged_manifest/release/processReleaseMainManifest/AndroidManifest.xml", + "build/app/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml" + ], + "device_delivery_verified": false + } + } + ] +} diff --git a/plans/audit-2026-09-23/issues/A14.md b/plans/audit-2026-09-23/issues/A14.md index 35459bb6..6cb29cb9 100644 --- a/plans/audit-2026-09-23/issues/A14.md +++ b/plans/audit-2026-09-23/issues/A14.md @@ -232,3 +232,10 @@ A13 커밋 `59d92bdd` 이후 구현했으며, 현재 기반 HEAD는 `e2fbdf0bb23 구현 커밋은 `282db21f5effa86eb2f82c81c247d57a0a6acfd0`다. Root가 source24개 SHA256을 독립 대조했고 최종 full772·analyze 로그를 확인했다. 새 KO/EN My Page 정리 확인 상태 PNG 두 장을 직접 열어 새 문구에 잘림이 없음을 확인했다. 기존 EN 화면의 한국어 혼재는 U05 후속 범위이며 A14 전체 번역 완료를 뜻하지 않는다. 원격 CI는 이 커밋을 push한 뒤 해당 head 기준으로 확인한다. [KO 상태 캡처](https://github.com/DevKor-github/OnTime-front/blob/282db21f5effa86eb2f82c81c247d57a0a6acfd0/plans/audit-2026-09-23/artifacts/a14/cleanup-needed-ko.png) · [EN 상태 캡처](https://github.com/DevKor-github/OnTime-front/blob/282db21f5effa86eb2f82c81c247d57a0a6acfd0/plans/audit-2026-09-23/artifacts/a14/cleanup-needed-en.png) + + +## 최종 원격 CI와 후속 실제 저장소 검토 + +head `b81259bf66881289bf6bca9ac7cca2548de6a326`의 [Flutter run35887794993](https://github.com/DevKor-github/OnTime-front/actions/runs/35887794993)은 전체772 tests/analyze 및 coverage87.44%(11487/13137)를 통과했다. [Android run35887794956](https://github.com/DevKor-github/OnTime-front/actions/runs/35887794956)도 release APK/manifest를 통과했다. artifact10763053548, synthetic merge3ebcf19237dcdd87e262c176762149dfd7ef0e8b, APK SHA2567e88d6882e3d1060965b33e7d6bab12e04f5aff3a4be5448e1ec859f8c314b60을 read-back했다. receipt는 a14-ci-validation.json이다. + +이후 A15 통합 리뷰에서 실제 AlarmRegistryRepositoryImpl.replaceAll이 scheduleId로 dedup하는 경계를 확인했다. 같은 일정에 서로 다른 provider/실제 platform ID의 취소 ownership이 여러 개 있으면 하나를 잃을 수 있다. 기존 fake registry 중심 회귀와772개 통과가 이 실제 persistence 문제를 검증한 것은 아니다. A15 #594에서 플랫폼 소유 ID 기준 보존, 동일 일정의 keeper 외 취소·실패 보존, 실제 repo/data source 왕복 회귀를 구현 중이며 아직 통과·완료로 기록하지 않는다. 이 후속 통합과 실기기 조건이 남아 A14 이슈는 OPEN으로 유지한다. diff --git a/plans/audit-2026-09-23/issues/D04.md b/plans/audit-2026-09-23/issues/D04.md new file mode 100644 index 00000000..569af6b4 --- /dev/null +++ b/plans/audit-2026-09-23/issues/D04.md @@ -0,0 +1,219 @@ +# [P1][D04] 암호화 저장소의 v1→v2 마이그레이션을 원자적으로 수행하고 과거 schema·실패 보존을 검증 + +## 현재 문제와 감사 기준 정정 + +현재 앱에는 schema 2와 v1→v2 마이그레이션이 있다. 2026-09-23 감사의 “DB v1의 onUpgrade는 명시적으로 실패한다 / 다음 schema 변경 전 기반이 필요하다”는 당시 관찰이며, 반복 일정 upstream 반영 이후 현재 결함을 그대로 설명하지 않는다. 기존 일회성 일정을 보존하면서 반복 일정용 4개 테이블과 5개 schedule column을 추가하는 구현과 파일 재열기 테스트가 이미 있다. + +남은 문제는 실제 업그레이드의 원자성, 미래 버전 입력 거부, 독립적인 역사 schema 기준, 실제 암호화 엔진 연결 및 실패 보존 증거다. 현 `onUpgrade`는 DDL을 차례로 실행하며 transaction을 만들지 않는다. locked Drift의 native migration 경로도 이를 자동 transaction으로 감싸지 않는다. 중간 실패 시 앞 DDL만 남고 버전은 이전 값인 상태가 가능하다. 또한 from=3, to=2 같은 미래 파일 입력은 현재 guard를 통과하여 버전이 낮아질 수 있다. + +네이티브 opener는 `PRAGMA key`를 실행하지만 실제 SQLCipher 엔진인지 확인하지 않는다. Android SQLCipher loader override도 없다. 이는 암호화가 입증되지 않은 위험 경계이며 **현재 APK에서 평문 유출이 실제 확인됐다는 뜻은 아니다**. 실제 번들의 암호화 연결을 검증하지 않고 host plain SQLite 성공만으로 암호화 마이그레이션 완료를 주장할 수 없다. + +우선순위 **P1**. 조사 기준 **`b81259bf66881289bf6bca9ac7cca2548de6a326`**. 이 문서는 전담 `grill-with-docs`의 조사와 root 결정 기록이다. 구현·새 테스트 실행·실기기 검증은 아직 수행하지 않았다. 다른 agent의 A15 WIP는 수정하지 않았고 D04의 DB/migration/opener 근거 파일은 기준 SHA와 동일함을 확인했다. + +## 고정 근거 + +| 근거 | 확인한 사실 | +|---|---| +| [database.dart:54–83](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/lib/core/database/database.dart#L54-L83) | schemaVersion=2. v1→v2 DDL 9개를 순차 실행. `from < 1 || to > schemaVersion` guard는 미래 from을 차단하지 않는다. | +| [recurring_migration_test.dart:14–80](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/test/core/database/recurring_migration_test.dart#L14-L80) | 현재 v2 생성 후 신규 테이블/컬럼을 제거하여 v1 파일을 만들고 재열기. 일정 이름, 사용자 note/score, 빈 recurring, default 및 FK를 검사. 현재 모델에 종속되는 fixture다. | +| [database_index_test.dart:32–38](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/test/core/database/database_index_test.dart#L32-L38) | onUpgrade 0→1 직접 호출 거부 테스트. 실제 미래 버전 파일/중간 DDL 실패/암호화 migration 증거가 아니다. | +| [역사 v1 database](https://github.com/DevKor-github/OnTime-front/blob/ccc6272bde15937a87dd577157b3330593718759/lib/core/database/database.dart), [역사 table 정의](https://github.com/DevKor-github/OnTime-front/tree/ccc6272bde15937a87dd577157b3330593718759/lib/data/tables) | local-only 도입 commit은 schema1/7개 table의 독립 출처다. | +| [반복 일정 도입 commit](https://github.com/DevKor-github/OnTime-front/commit/f0877d1df9aa62528f360f5ee1d3bbb095e2f691) | 현재 v2 추가 마이그레이션의 출처. | +| [Schedules:6–43](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/lib/data/tables/schedules_table.dart#L6-L43), [recurring tables](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/lib/data/tables/recurring_schedule_tables.dart) | 기존 일정 상태·score 기여·timezone·준비 동결을 유지하고 신규 관계/default를 검증해야 한다. | +| [Native opener:6–24](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/lib/core/database/open_database_native.dart#L6-L24) | 파일/키 준비 후 background DB에서 PRAGMA key와 cipher_memory_security 실행. cipher_version/Android loader 설정 없음. | +| [Key store:21–40](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/lib/core/database/installation_key_store.dart#L21-L40) | missing key의 getOrCreate 동작은 별도 D01 보존 guard가 담당. D04가 잘못된 키 문제를 새 키 발급으로 해결하면 안 된다. | +| [Lock:267–282](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/pubspec.lock#L267-L282), [Lock:1124–1139](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/pubspec.lock#L1124-L1139) | Drift/drift_dev 2.31.0, sqlcipher_flutter_libs 0.6.8, sqlite3 2.9.4. 조사한 dependency 내부의 버전 근거. | +| [Flutter CI](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/.github/workflows/flutter_test.yml) | 생성/analyze/test/coverage는 실행하지만 역사 snapshot 누락·지원 migration 경로 완전성이나 실제 모바일 SQLCipher를 직접 증명하는 gate는 없다. | +| [Architecture DB 예시:220–233](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/docs/Architecture.md#L220-L233) | schemaVersion=3 예시가 현재 코드와 다르다. DB 부분 최소 정정 대상. | + +역사 commit과 기준 HEAD의 기존 7개 table 정의를 `git diff`로 비교했으며 schedules에 반복 일정 컬럼 5개가 추가된 것 외 나머지 6개 table 정의는 같았다. **기존 역제거 fixture가 잘못됐다고 결론 내리지 않는다.** 향후 현재 정의가 바뀌면 같은 테스트 생성물도 같이 바뀌므로 독립적인 역사 계약을 증명하지 못한다는 한계를 수정한다. + +### Locked dependency 내부 조사 근거 + +다음은 설치된 pub cache의 실제 파일을 읽은 결과다. 최신 온라인 문서를 임의 적용한 것이 아니며 lock 변경 시 재검증한다. + +- `drift-2.31.0/lib/src/runtime/executor/helpers/engines.dart:495–530`, `DelegatedDatabase._runMigrations`: `beforeOpen`을 호출한 다음 `setSchemaVersion`을 실행한다. 해당 경로에서 전체 migration을 자동 transaction으로 묶지 않는다. 515–518은 user_version=0을 신규로 정규화한다. +- `drift-2.31.0/lib/src/runtime/api/db_base.dart:118–138`, `GeneratedDatabase.beforeOpen`: onCreate/onUpgrade를 직접 await하고 그 뒤 beforeOpen hook을 실행한다. +- `drift-2.31.0/lib/src/runtime/query_builder/migration.dart:609–628`, `OpeningDetails.hadUpgrade`: 이전 버전과 새 버전이 다르면 참이다. 버전 감소도 포함된다. +- `sqlite3-2.9.4/lib/src/ffi/load_library.dart:32–48`, `_defaultOpen`: Android 기본 loader는 `libsqlite3.so`다. +- `sqlcipher_flutter_libs-0.6.8/README.md:24–38,55–72,77–93`: Android SQLCipher loader 선택, background isolate에서도 별도 설정, cipher_version 검사 필요를 설명한다. 일반 SQLite에서 PRAGMA key를 실행하는 것만으로 암호화가 확인되지 않는다. +- `sqlcipher_flutter_libs-0.6.8/lib/sqlcipher_flutter_libs.dart`, `openCipherOnAndroid`: `libsqlcipher.so`를 선택하는 실제 함수다. + +## 제품 계약과 용어 + +[CONTEXT의 Local Data Store/Recovery Mode](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/CONTEXT.md#L413-L420), [ADR 0018](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/docs/adr/0018-encrypt-active-data-with-a-device-bound-key.md), [ADR 0022](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/docs/adr/0022-use-encrypted-drift-as-the-local-data-source.md), [ADR 0023](https://github.com/DevKor-github/OnTime-front/blob/b81259bf66881289bf6bca9ac7cca2548de6a326/docs/adr/0023-preserve-local-data-when-migration-fails.md)를 따른다. + +- Local Data Store의 schema version과 portable OnTime Backup의 format version은 다르다. D04는 schema migration이며 backup format 1/2 정책을 변경하지 않는다. +- schema1은 local-only 암호화 저장소의 역사적 schema다. 이전 서버 기반/legacy DB를 버전 숫자 하나만 보고 schema1로 간주하지 않는다. +- migration 성공은 저장소 변환의 사실이다. 복원, 준비 자동 시작, OS 알림 도착 또는 새 key 생성과 같은 뜻이 아니다. +- 새 제품 용어 결정은 없다. 기존 원자적 migration/보존 Recovery 계약을 구현하는 작업이므로 glossary에 구현 상세를 추가하지 않는다. 기존 ADR 0018/0023과 충돌하는 새 정책도 없으므로 새 ADR를 불필요하게 만들지 않는다. + +## 확정 범위 + +### 실제 migration과 지원 버전 + +1. 역사적 local v1→현 v2 경로를 유지하고 실제 파일에서 transaction의 원자성을 보장한다. 성공한 전체 변경에만 schema version을 적용한다. +2. callback DDL만 transaction으로 감싼 뒤 version 기록이 외부에 남는다는 이유로 완료 판정하지 않는다. Drift의 실제 version 적용/commit 순서, beforeOpen 실패와 재열기까지 검증하여 반쯤 바뀐 schema와 이전 version 조합을 남기지 않는다. 구체 API는 구현 시 locked source에 맞춰 선택한다. +3. 기존 부분 DDL/알 수 없는 미래 schema는 `IF NOT EXISTS`, DROP, 자동 재생성으로 숨기지 않는다. 보존한 제한 Recovery로 보낸다. +4. v2 재열기는 no-op이어야 하며 일정/점수/준비 이력/재료화 결과를 다시 쓰거나 revision을 증가시키지 않는다. fresh v2와 upgraded v2의 의미적 schema를 비교한다. +5. D01 신규 설치/기존 store 구분을 따른다. 신규로 증명된 empty store는 허용하되 비어 있지 않은 user_version=0 또는 중단 파일을 신규 설치로 추정하지 않는다. + +### 실제 암호화 opener + +1. 실제 SQLite를 사용하는 isolate에서 첫 SQLite API 이전 Android SQLCipher loader를 설정한다. UI isolate 설정만으로 background 연결이 안전하다고 추정하지 않는다. +2. `cipher_version` 결과가 없거나 빈 값/조회 오류이면 schema read, migration, durable write를 시작하지 않는다. typed 실패와 자원 close를 D01 Recovery 경계로 전달한다. +3. 올바른 기존 Installation Data Key 적용 후 schema를 실제로 읽어 키/파일 조합을 검증한다. getOrCreate로 새 키를 발급해 실패를 덮지 않는다. +4. 기존 plaintext 가능 파일은 보존하며 자동 변환/삭제하지 않는다. D02의 검증된 백업 복원 또는 명시적 reset은 별도 사용자 작업이다. +5. 테스트 전용 plain NativeDatabase 경로는 순수 migration 로직 테스트에 사용할 수 있다. 제품 opener의 cipher guard를 테스트 편의를 위해 끄거나 약하게 만들지 않는다. +6. Android/iOS 실제 번들, 해당 번들의 SQLCipher 라이브러리와 실제 파일 경로로 encrypted historical v1 생성→v2 upgrade→close/reopen→잘못된 key 거부→일반 SQLite 읽기 거부를 검증한다. host/mock 성공은 이 증거를 대체하지 않는다. + +### 독립 역사 자료와 CI + +- schema1의 독립 출처는 `ccc6272bde15937a87dd577157b3330593718759`다. 그 source에서 생성한 snapshot/DDL과 합성 fixture를 생성 명령, tool/engine 버전, source commit, 파일 hash와 함께 추적한다. 실제 사용자 DB를 fixture에 넣지 않는다. +- 현재 entity의 직렬화 결과만으로 과거 데이터를 만들지 않는다. 과거 table/column/default/제약/인덱스/참조 및 당시 값을 고정한다. +- 현재 schema2 snapshot을 별도로 둔다. 지원 버전 목록과 경로는 명시적으로 유지하고 schema bump에 snapshot이나 경로가 빠지면 CI가 실패하도록 한다. +- JSON/SQL/합성 fixture는 의도된 기준 자료다. `*.g.dart`, `*.config.dart`, `*.freezed.dart`, `*.mocks.dart`는 기존 정책대로 로컬/CI에서 재생성하며 커밋하지 않는다. +- 기존 역제거 테스트는 보조로 유지 가능하다. 역사 fixture와 실제 migration opener 검증을 대체하지 않는다. +- Architecture의 DB schema 예시만 현재 계약에 맞게 정정한다. 서버/로그인 등 전체 문서 정리는 O01에서 수행한다. + +## 의존성과 제외 범위 + +| 항목 | 연결 | +|---|---| +| D01 [#603](https://github.com/DevKor-github/OnTime-front/issues/603) | DB 비의존 shell, 신규/기존 store 및 key guard, typed Recovery, 실패 자원 정리와 안전 재시도. D04가 별도 startup engine을 만들지 않는다. | +| D02 [#604](https://github.com/DevKor-github/OnTime-front/issues/604) | 손상 DB/key를 보존하고 검증 후보로 실제 교체하는 복구. D04는 자동 restore/key rotation을 추가하지 않는다. | +| A09 [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | staging DB 역시 같은 검증된 opener/schema 계약을 쓴다. normal restore의 data replacement/runtime 무효화는 A09 담당. | +| D03 [#595](https://github.com/DevKor-github/OnTime-front/issues/595), A15 [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 승인된 reset/restore 소유권과 실제 writer quiesce를 따른다. migration 오류가 reset 의도를 뜻하지 않는다. | +| T02 | 모바일 native DB/암호화 회귀를 지속 자동화. D04 실제 암호화 AC를 T02로 넘겨 삭제하거나 fake-only 완료로 만들지 않는다. | +| O01 | 전반적 architecture 문서의 과거 서버/로그인 표현 정리. D04에서는 DB 예시만 최소 정정. | + +제외: 서버/로그인/클라우드 migration, 기존 plaintext 파일의 묵시적 암호화 변환, 암호 suite/backup format 교체, 임의 데이터 복구/손상 행 삭제, schema3 기능 설계, 범용 migration 프레임워크, power-loss 절대 보장, 실제 사용자 DB/키를 저장소나 로그에 노출하기. + +## 실패·중단 결과 계약 + +- 정상 실패 이전에 확정되지 않은 migration은 기존 v1의 논리적 schema/data/user_version을 보존한다. 다만 외부 호출이 오류를 반환했어도 권위 있는 재열기가 완전한 v2를 보여주면 “v1으로 rollback됐다”고 표시하지 않는다. 응답과 commit 사실을 구분하고 현재 저장소 상태에 맞는 Recovery/재시도를 제공한다. +- 실제 같은 원자 경계에서 schema와 version이 확정되는지 검증한다. DDL commit 후 Drift의 version write가 별도로 실패하는 gap을 방치하지 않는다. +- 오류 fixture는 실제 SQLite 파일과 독립 연결을 사용한다. DDL별 실패, version/commit 실패 및 `SQLITE_FULL`, read-only, locked, constraint 상태가 실제로 발생했는지 코드·명령·관측 오류 코드로 입증한다. 아직 지정하지 않은 fault injection 기법은 구현 시 선택하고 기록한다. 어려운 경계를 단순 Future throw로 대체하지 않는다. +- 앱/별도 process를 강제 종료한 뒤 실제 재열기에서 이전 v1 또는 완전 v2만 허용한다. version만 바뀌거나 일부 DDL만 남은 중간 상태를 성공으로 취급하지 않는다. process-kill 검증을 기기 전원 손실 내구성 보장으로 확대하지 않는다. +- rollback 시 DB 파일 바이트 전체 hash가 항상 같아야 한다고 요구하지 않는다. SQLite journal/WAL/checkpoint가 물리적 배치를 바꿀 수 있다. 논리적 데이터·schema·version, key identity, 소유 파일군 보존을 검사한다. wrong-key/거부 경로에서는 불필요한 변경이 없다는 별도 증거도 수집한다. +- 파일 존재 여부 하나로 보존 성공을 판단하지 않는다. 원래 관계/데이터를 기존 schema의 독립 reader로 읽고 integrity/FK 상태를 확인한다. key 값은 출력하지 않고 테스트 소유 identity 또는 비노출 비교를 사용한다. +- 실패를 D01 typed Recovery로 전달하고 정상 UI, Schedule Notification 재등록, 준비 자동 시작을 차단한다. 정상 startup 재시도는 D01 단일 attempt를 재사용한다. 실패한 DB handle/백그라운드 isolate를 남긴 채 새 연결을 중첩하지 않는다. + +## 수용 기준 + +- [ ] 감사의 과거 “migration 없음” 표현을 현재 v1→v2 존재와 검증 공백으로 정정한다. +- [ ] 역사 v1 snapshot/DDL/합성 데이터 fixture 및 provenance가 현재 모델과 독립적이다. +- [ ] 정상 historical v1 파일을 actual onUpgrade/opener 경로로 upgrade하고 7개 표의 모든 durable 필드·관계·인덱스·제약·default를 보존한다. +- [ ] 신규 recurring 테이블/컬럼은 올바른 empty/default 상태이고 기존 일정이 임의 recurring으로 바뀌지 않는다. +- [ ] upgraded v2와 fresh v2가 의미적으로 동등하며 v2 재열기는 데이터/이력/revision을 바꾸지 않는다. +- [ ] 미래 버전, 지원하지 않는 predecessor, 비어 있지 않은 version0, 이미 부분 DDL인 파일을 보존 거부한다. 실제 파일의 version이 낮아지지 않는다. +- [ ] 모든 DDL과 version/commit 원자 경계에서 실패 후 독립 재열기가 v1 또는 완전 v2만 보여준다. 애매한 응답은 권위 있는 상태로 판정한다. +- [ ] 실제 SQLite FULL/read-only/locked/constraint 오류와 별도 process-kill 결과를 기록한다. mock-only 성공으로 대체하지 않는다. +- [ ] 각 SQLite 사용 isolate의 Android loader 선택과 cipher_version fail-closed guard가 제품 opener에 적용된다. +- [ ] 키 적용 후 schema read를 검증하고 missing/invalid/wrong key/cipher 실패에서 기존 파일군·key를 보존하고 연결을 닫는다. 자동 key 생성·plaintext fallback이 없다. +- [ ] Android/iOS 실제 번들에서 암호화 historical v1→v2, close/reopen, wrong-key 거부, 일반 SQLite 읽기 거부를 증명한다. +- [ ] D01 Recovery UI/정상 화면·알림 차단 통합 검증이 있다. 호출 오류를 무조건 rollback으로 표시하지 않는다. +- [ ] schema bump/snapshot/지원 경로 누락이 CI에서 실패한다. 생성 Dart는 커밋하지 않는다. +- [ ] DB Architecture 예시 최소 정정, 재현 명령/환경/commit/결과/한계를 issue와 PR에 기록한다. + +## 검증 행렬 + +| 번호 | 시나리오 | 필요한 검증과 기대 결과 | +|---|---|---| +| 1 | 역사 v1 provenance | 고정 source commit/tool 버전/명령/hash로 fixture 재생성. 현재 entities에 의존하지 않음. | +| 2 | populated v1→v2 | 실제 onUpgrade 호출 후 모든 표·행·필드 보존, 새로운 관계/default만 추가. | +| 3 | empty local v1 | 빈 정당한 과거 store가 정상 v2로 변환, profile/score 자동 합성 없음. | +| 4 | 한글·null·공백·길이 경계 | durable 텍스트/nullable 필드 보존, 임의 normalization 없음. | +| 5 | civil time/timezone/overlap offset | 일정의 civil date/time, named zone, 선택 offset 보존. locale/device zone에 따라 commitment 이동 없음. | +| 6 | started/finished/frozen/outcome | 기존 진행/완료 facts와 score contribution 유지, timer 자동 복원 없음. | +| 7 | score와 user preferences/revisions | aggregate counts, alarms/detail preference, export/revision 관련 durable 값 보존. | +| 8 | default/custom/template 준비 관계 | 단계 순서/연결, 소유권 및 삭제된 template snapshot 사실 보존. | +| 9 | 인덱스/제약/default/FK | sqlite metadata와 제약 위반 실제 동작을 검증. foreign_key_check 및 integrity 확인. | +| 10 | v2 schema 동등성 | fresh v2와 upgraded v2의 의미적 정의 일치. SQL 문자열의 공백 차이만 실패로 만들지 않음. | +| 11 | v2 재열기 두 번 | migration 반복 없음, rows/revisions/createdAt 수정 없음. | +| 12 | 미래 version3 파일 | actual open 거부, user_version=3 및 unknown table/rows 보존. | +| 13 | version0+기존 tables/sidecars | 신규로 추정하지 않고 D01 Recovery, createAll 실행 없음. | +| 14 | unsupported predecessor/legacy 서버 파일 | local v1이라고 추정하지 않고 보존. | +| 15 | 이미 일부 recurring DDL인 v1 | IF NOT EXISTS/DROP으로 정상처럼 만들지 않고 보존 거부. | +| 16 | DDL 9개 각 전후 실패 | 실제 파일/실패 위치별 독립 재열기로 v1 또는 완전 v2만 확인. | +| 17 | schema commit/version write 경계 | DDL transaction만 성공하고 이전 version이 남는 gap 없음. 실패 응답도 authoritative reopen으로 판정. | +| 18 | beforeOpen validation 실패 | commit 상태에 맞는 보존 Recovery, 불완전 v2/거짓 rollback 보고 없음. | +| 19 | 실제 SQLITE_FULL | 제한된 테스트 환경에서 실제 SQLite 오류 code 발생 확인, 전체 디스크를 채우지 않음. 파일군/키 보존. | +| 20 | 실제 read-only/locked | 각 실제 SQLite 오류를 관측, 오류 해소 뒤 안전한 독립 retry. 무한 자동 retry 없음. | +| 21 | 실제 constraint 실패 | 원자 rollback 또는 이미 완전 commit된 상태를 정확히 판정. 부분 관계 없음. | +| 22 | 별도 process 종료 | 각 commit 전후 중단 후 새 process에서 기존 v1 또는 완전 v2만 관측. power-loss 주장 금지. | +| 23 | cipher_version 없음/빈 값/오류 | schema read/write/migration 전 fail-closed, connection close, typed Recovery. | +| 24 | Android UI/background isolate | 실제 사용하는 isolate 각각 올바른 SQLCipher loader와 nonempty cipher_version. | +| 25 | iOS native bundle | 실제 링크된 엔진으로 encrypted 파일을 확인. SQLCipher pod 존재만으로 통과시키지 않음. | +| 26 | wrong key/malformed/missing/unreadable key | D01 existing-store guard 및 실제 wrong-key read 실패, key 교체/blank DB 없음. | +| 27 | plaintext 가능 기존 파일 | 무손실 보존 거부, 자동 암호화 변환/삭제 없음. | +| 28 | 모바일 encrypted historical migration | Android/iOS 실제 빌드에서 v1 생성/upgrade/reopen 및 일반 SQLite로 읽기 거부. | +| 29 | Recovery 통합 | 정상 화면, notification reconciliation, automatic preparation 시작 없음. typed 오류 문구와 단일 안전 retry. | +| 30 | CI schema guard 음성 사례 | snapshot 삭제/잘못된 version/지원 경로 누락 시 실제 CI 명령 실패, 정상자료에 통과. | +| 31 | 로그/fixture privacy | 합성 데이터만 사용, 실제 key/password/private schedule/path 노출 없음. | + +각 결과에 코드 SHA, Flutter/Dart/Drift/SQLite/SQLCipher 버전, 플랫폼/기기 또는 emulator/simulator 여부, 명령, exit/result, fixture identity, 재열기 관측을 남긴다. host 논리 테스트, fault 주입, 모바일 native 암호화 및 실기기 검증을 별도 표시한다. 실행하지 않은 행은 미실행으로 남기고 완료 처리하지 않는다. + +## 실제 grill-with-docs 전체 문답 + +전담 agent가 스킬을 직접 읽고 코드로 답할 수 있는 사실을 조사한 다음 root에게 질문을 하나씩 보냈다. 아래 추천은 각 질문 시점의 제안이며 root의 확정 답이 최종 계약이다. D04.md 외 소스·테스트·공유 tracking·GitHub를 수정하지 않았다. + +### Q1 — 현 v2를 반영한 문제 재정의 + +**Agent 질문:** + +> D04 Q1: 현재는 schema 2와 실제 v1→v2 추가 마이그레이션이 존재하므로 감사의 'migration 기반 없음'은 정정해야 합니다. 다만 database.dart:61-77은 DDL 9개를 transaction 없이 순서 실행하고, 설치된 Drift 2.31.0의 _runMigrations도 beforeOpen을 transaction으로 감싸지 않습니다. 중간 DDL 실패 시 앞서 생성한 테이블만 남고 user_version은 1인 상태가 될 수 있습니다. D04를 '실제 v1→v2 원자성 보강 + 역사적 schema 계약 + 실패 보존 검증'으로 확정할까요? 추천: 예. 기존 v1 지원은 유지하고 성공한 전체 변경에만 schema version을 적용하며, 중간 실패 후 재오픈 재시도가 원본 v1에서 가능해야 합니다. 이미 실패로 일부 DDL이 남은 v1 파일은 확인 없이 DROP/IF NOT EXISTS로 숨기지 말고 D01 Recovery로 보존합니다. D01 시작/키 가드 및 D02 복구 교체와 소유 범위를 분리합니다. + +**Agent 조사 보충:** + +> D04 보충 조사: 역사 기준 ccc6272bde15937a87dd577157b3330593718759의 기존 7개 table과 현재 차이를 직접 비교했습니다. schedules의 5개 recurring column 추가만 있고 나머지 6개 table은 동일합니다. 따라서 현재 역제거 fixture가 곧 잘못됐다는 주장은 하지 않겠습니다. 문제는 fixture가 현재 정의에 종속되어 향후 변경을 독립적으로 검출하지 못하고 실패 경계를 검증하지 않는 점입니다. docs/Architecture.md의 schemaVersion=3 예시는 실제 schema2와 불일치하여 D04에서 DB 부분만 최소 정정 후보로 기록하겠습니다. + +**Root 답변:** + +> Q1을 확정합니다. 현재 v1→v2 구현 존재를 먼저 정정하고, 실제 DDL 전체의 원자성·역사적 schema 계약·중간 실패 보존을 D04 범위로 삼습니다. DDL 중간 실패/재오픈에서 원래 v1 내용과 user_version이 보존돼야 하며, 이미 부분 DDL이 남은 입력은 IF NOT EXISTS/DROP으로 조용히 덮지 않고 D01 Recovery에서 보존합니다. D01/D02 책임을 중복하지 않습니다. 추가 조사할 사실: root가 locked sqlcipher_flutter_libs0.6.8 README를 읽었는데 Android sqlite3 loader override(openCipherOnAndroid), background isolate에서도 override, PRAGMA cipher_version 확인이 명시돼 있습니다. 현재 open_database_native는 PRAGMA key만 실행하고 lib 전체 rg에서 override/cipher_version이 없습니다. sqlite3 2.9.4 default loader는 libsqlite3.so를 엽니다. 실제 APK plaintext라고 아직 단정할 수는 없지만 D04의 '실제 암호화 마이그레이션' 근거는 이 bootstrap 경계 검증이 없으면 약합니다. 이 현황을 직접 확인하고 D04 구현에 필요한 최소 cipher fail-closed guard와 native 검증/T02 의존 경계를 다음 질문에서 제안해 주세요. 추가 무관 기능을 섞지는 않습니다. + +**Root 조사 보충에 대한 답변:** + +> 보충 조사 구분에 동의합니다. 현재 역제거 fixture가 틀렸다고 쓰지 말고 현재 코드 종속성 때문에 역사 계약을 독립 검증하지 못한다는 한계를 명시하세요. 역사 commit의 7개 table/인덱스/제약/사용자 데이터 표본을 고정할 근거가 생겼습니다. Architecture의 DB schema 예시는 D04에서 최소 정정하고 나머지 서버/로그인 문서 전면 정리는 O01에 남깁니다. + +### Q2 — 실제 암호화 엔진 경계 + +**Agent 질문:** + +> D04 Q2: locked sqlcipher_flutter_libs 0.6.8 README와 sqlite3 2.9.4 loader를 직접 확인했습니다. 실제 background isolate가 쓰는 Android loader override와 cipher_version 검증이 lib에 없습니다. PRAGMA key는 일반 SQLite에서도 조용히 무시될 수 있어, 현재 '실제 암호화'는 입증되지 않았습니다. 추천 결정: D04에 최소 native opener 수정(각 SQLite 사용 isolate에서 Android SQLCipher loader 선택, schema/read/write 전에 nonempty cipher_version 확인, 정확한 키로 schema read 검증, 실패 시 close+typed Recovery)을 포함합니다. 이미 존재하는 plaintext 가능 파일은 자동 삭제·덮어쓰기·묵시적 암호화 변환하지 않고 D01/D02 보존 경로로 보냅니다. fake cipher_version/host plain SQLite 테스트는 로직 검증만 인정하고, Android/iOS 실제 번들로 암호화 v1→v2/reopen·wrong key·plain SQLite 읽기 거부를 통합 검증해야 D04를 완료합니다. T02는 이를 재실행 가능한 native CI로 확장하며 D04 필수 증거를 뒤로 미루는 의존성이 아닙니다. 이 범위로 확정할까요? + +**Agent 추가 발견:** + +> D04 추가 결함을 찾았습니다. Drift OpeningDetails.hadUpgrade는 from != to여서 downgrade도 onUpgrade로 들어갑니다. 현재 guard는 from < 1 || to > schemaVersion뿐이라 미래 schema 3 파일을 schema2 앱이 열면 from=3,to=2가 거부되지 않고 DDL 없이 반환하며 Drift가 user_version을 2로 낮출 수 있습니다. 다음 질문의 실패/호환성 경계에 이 실제 경로를 포함하겠습니다. 미래 버전 입력은 보존 거부해야 합니다. + +**Root 답변:** + +> Q2를 확정합니다. 실제 SQLite 사용 isolate에서 Android SQLCipher loader를 선택하고, cipher_version 비어 있음/조회 오류 시 schema read·migration·write를 시작하지 않는 fail-closed 경계를 D04에 포함합니다. 키 적용 후 실제 schema read 검증과 실패 close/typed Recovery를 연결합니다. '정확한 키'를 새 키 발급으로 충족시키면 안 되며 existing-store/key 생성 판단은 D01 guard를 따릅니다. 기존 plaintext 가능 파일은 자동 삭제·변환하지 않고 보존합니다. 실제 모바일 번들에서 encrypted historical v1→v2 및 재열기/잘못된 키/일반 SQLite 읽기 거부까지 검증해야 완료이며 host plain SQLite와 mocked cipher_version은 그 대체가 아닙니다. T02 자동화는 후속 확장이지만 이 암호화 검증 AC를 삭제하지 않습니다. 현 상태를 'Android에서 평문 유출이 확인됨'이라고 단정하지 말고 loader/guard 누락과 아직 입증되지 않은 플랫폼 상태를 정확히 구분하세요. + +### Q3 — 독립 역사 fixture와 CI 계약 + +**Agent 질문:** + +> D04 Q3: 역사적 fixture와 지속 검증 계약을 어떻게 고정할까요? 추천: ccc6272bde15937a87dd577157b3330593718759의 schema1 소스에서 독립 생성한 schema snapshot/DDL 및 합성 데이터 fixture를 provenance(생성 commit, tool 버전, 재생성 명령, hash)와 함께 추적하고, 현 schema2 snapshot을 별도로 추적합니다. 현재 v2를 역제거하는 기존 테스트는 보조로 남길 수 있으나 유일한 과거 증거로 쓰지 않습니다. 7개 표의 모든 durable 필드, 인덱스·제약·기본값·FK와 한글/null/타임존 중첩 시각/완료·진행 이력/score 및 template/preparation 연결을 비교하고 v2 신규 recurring defaults를 확인합니다. fresh v2와 upgraded v2의 의미적 schema 동등성을 검증하며, 향후 schema bump마다 snapshot과 모든 지원 버전 경로 테스트를 CI에서 강제합니다. JSON/SQL/합성 fixture는 의도된 기준 자료로 추적하되 생성 Dart 출력은 기존 ignore 정책을 유지합니다. 동의할까요? + +**Root 답변:** + +> Q3을 확정합니다. 고정 역사 commit의 schema1에서 독립 생성한 snapshot/DDL·합성 fixture와 provenance를 추적하고 현재v2 snapshot을 별도로 유지합니다. 미래 fixture가 현재 entity 직렬화에만 의존해 과거 필드 손실을 숨기지 않게 실제 과거 형태/값을 고정하세요. 일곱 표의 durable 필드와 관계·인덱스·제약·기본값, 완료/진행 facts/score, 한글/null/타임존 경계를 포함하며 fresh/upgraded v2 의미적 동등성을 비교합니다. 생성 Dart는 ignore 유지, 의도된 SQL/JSON/합성 fixture만 추적합니다. schema bump마다 snapshot/지원 경로 누락이 CI에서 실패하는 재현 가능한 검사를 포함합니다. 기존 역제거 테스트는 보조 증거로 유지할 수 있습니다. 미래 버전 downgrade 경로도 다음 문답에서 보존 거부 계약을 확정하겠습니다. 패키지 내부 근거는 locked version 파일/함수와 줄 위치를 명시해 주세요. + +### Q4 — 실패와 중단의 실제 보존 증거 + +**Agent 질문:** + +> D04 Q4: 실패·중단 검증의 완료 기준을 확정하겠습니다. 추천: (1) 실제 opener에서 미래 user_version, unsupported local predecessor, 비어 있지 않은 version0/부분 DDL 입력을 읽기만 하고 보존 거부하며 legacy server DB를 local v1로 추정하지 않습니다. (2) 각 DDL 전후, version 기록/commit 경계, SQLite FULL/read-only/locked/constraint 오류를 실제 파일에서 주입하고 연결을 닫은 뒤 독립 재열기로 schema/data/user_version/key identity 보존을 검사합니다. 단순 Future throw는 fault handling만 증명하며 SQLite rollback/암호화 검증을 대신하지 않습니다. (3) 실제 별도 process 종료 후 재열기로 v1 또는 완전 v2만 남는지 확인하되 power-loss 보장으로 확대하지 않습니다. (4) 파일 hash 전체 동일을 일반 rollback 기준으로 삼지 않습니다. 정상 WAL/journal 동작이 바이트 배치를 바꿀 수 있으므로 논리적 원본 내용/schema/version/key와 파일군 보존을 기준으로 하되 wrong-key/거부 경로는 불필요한 변경이 없음을 별도 확인합니다. (5) 실패는 D01 typed Recovery, normal UI·알림 시작 차단, 성공만 1회 준비 완료입니다. 암호화 파일 오류·disk full을 fake-only로 통과시키지 않고 모바일 native 검증과 정확히 구분합니다. 이 정도를 D04 필수 AC로 할까요? + +**Root 답변:** + +> Q4를 확정합니다. 미래 버전/지원하지 않는 predecessor/비어 있지 않은 version0/이미 부분 DDL인 입력을 현재 schema로 조용히 낮추거나 수선하지 않고 보존 거부합니다. legacy server DB를 local v1로 추정하지 않습니다. 실제 파일에서 DDL·version·commit 실패, FULL/read-only/locked/constraint를 주입하고 독립 reopen으로 논리적 schema/data/version/key identity와 파일군 보존을 검사합니다. 파일 전체 hash 일치를 모든 rollback의 필요조건으로 삼지 않는 이유도 기록해 주세요. 별도 process kill 재열기는 v1 또는 완전v2만 허용하고 power-loss 내구성으로 확대하지 않습니다. fake-only 실패 주입, host SQLite, 실제 모바일 암호화 검증의 증거를 분리하며 D01 typed Recovery/정상 UI·알림 차단을 실제 통합합니다. 지금 원래 문답 4개 및 root/agent 보충 전체, locked dependency 내부 근거, 재평가된 현재 문제를 상세 D04.md로 작성·동결하세요. 구현 중 아직 미지정인 fault injection 방식은 각 실제 SQLite 오류가 발생했음을 확인하는 검증 명령/코드와 함께 결정하도록 명시하고, 어려운 테스트를 단순 throw로 대체하지 않습니다. + +**Root 최종 구현 경계 보충:** + +> 구현 시 주의할 추가 경계도 문서에 넣어 주세요. onUpgrade 안의 DDL만 transaction으로 묶고 Drift의 user_version 갱신이 바깥에서 일어나면 DDL commit→version write 실패 gap이 남습니다. 동일 원자 경계에서 schema와 version이 함께 확정되는 실제 방법을 locked Drift executor 경계로 검증해야 합니다. 외부 호출이 오류를 반환했더라도 authoritative reopen이 완전v2이면 'v1으로 rollback됨'이라고 표시하지 않고 현재 사실에 맞는 Recovery/재시도로 처리합니다. 이 사항은 구체 구현을 선결하는 것이 아니라 Q4의 v1 또는 완전v2 AC를 실질적으로 충족시키기 위한 경계입니다. + +## 구현 시 보고 및 완료 판정 + +구현 전 이 문서를 GitHub issue 본문으로 게시하고 authoritative read-back을 비교한다. 구현 결과에는 변경 SHA, 역사 fixture provenance, 실제 테스트 명령/결과, CI run, 모바일 검증 기기/빌드 및 남은 행을 추가한다. 소스 수정과 host suite 통과만으로 이슈를 닫지 않는다. 특히 실제 모바일 암호화, failure/reopen, D01 Recovery 통합이 미검증이면 해당 상태를 명시하고 issue를 OPEN으로 유지한다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 20fee29c..d26d11a5 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -290,7 +290,7 @@ "`reconcile_alarms_use_case.dart:204-230,529-538`; `my_page_screen.dart:203-214`", "permission grant/revoke·OS 예약 유실·재시작 후 provider와 OS pending/registry를 일치시킴. 불일치 시 취소·교체·정직한 상태 표시." ], - "status": "code_verified_ci_pending", + "status": "code_verified_followup_pending", "agent": "/root/grill_a14", "issue_number": 593, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/593", @@ -299,15 +299,17 @@ "pr_url": "https://github.com/DevKor-github/OnTime-front/pull/585", "verification": [ "Frozen source24 SHA256 matched; focused100/full772, analyzer, native Swift SDK/Kotlin compile, generated/local-only checks passed. a14-validation.json.", - "Root visually reviewed actual MyPage standalone KO/EN captures; fake receipts/in-memory DB, not physical OS screenshots." + "Root visually reviewed actual MyPage standalone KO/EN captures; fake receipts/in-memory DB, not physical OS screenshots.", + "Remote b81259bf: Flutter772/analyze, coverage87.44% (11487/13137), Android APK/manifest passed. a14-ci-validation.json." ], "blockers": [ - "Remote CI pending; actual Android/iOS delivery and permission/Doze/reboot matrix not executed." + "A15 integration follow-up: actual registry scheduleId dedup can lose multiple provider/platform-ID cancellation ownership; implementation underway.", + "Actual Android/iOS delivery and permission/Doze/reboot matrix not executed." ], "sequence": 9, "issue_body_sha256": "23769c09503d629f813df5b93db7b2b6fce715dd15ea1571ea09fa06b9c1a5d4", "readback_verified": true, - "body_sha256": "2c068b0a81fa6eae7e339bff394a6e492a71f8395cc37ad7a724a2b7652c85a8" + "body_sha256": "562f5779035405a4a284ba08f76067b19c50eddf6050aa6d5f47093ad3a4c470" }, { "audit_id": "A15", @@ -527,7 +529,7 @@ { "audit_id": "D04", "priority": "P1", - "summary": "DB v1의 onUpgrade는 명시적으로 실패한다. 현재 v1 자체의 장애라고 볼 수 없으나 다음 schema 변경 전 migration 기반이 필요하다.", + "summary": "현재 v1→v2 migration은 존재하지만 DDL/version 원자성·미래 버전 거부·독립 역사 fixture·실제 SQLCipher loader/guard 및 실패 보존 검증을 보강해야 한다.", "audit_cells": [ "D04", "**P1 · 개선**", @@ -535,16 +537,18 @@ "`database.dart:49-64`; `test/core/database/database_index_test.dart:32`", "version별 schema snapshot, 기존 파일 fixture, transaction rollback, 디스크 부족·암호키 오류 테스트. 실패 시 파일·키 보존." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "ready", + "agent": "/root/grill_d04", + "issue_number": 605, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/605", "decisions_file": "plans/audit-2026-09-23/issues/D04.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 19 + "sequence": 19, + "readback_verified": true, + "body_sha256": "6ab517bf1b1a2b59c81d52306941df653be2f81d01efaea44da3af0036575247" }, { "audit_id": "D05", From d2ae69d3f0e139d2d88c46a7f1e0b8da5e403830 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 01:42:33 +0900 Subject: [PATCH 28/59] docs: record bounded backup validation decisions for D05 --- handoff/audit-2026-09-23-progress.md | 70 +++--- plans/audit-2026-09-23/README.md | 3 +- plans/audit-2026-09-23/issues/D05.md | 200 ++++++++++++++++++ .../pre-a15-ci-validation.json | 42 ++++ plans/audit-2026-09-23/state.json | 12 +- 5 files changed, 282 insertions(+), 45 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/D05.md create mode 100644 plans/audit-2026-09-23/pre-a15-ci-validation.json diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index a5db3ee1..4435ec91 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -1,39 +1,45 @@ # OnTime 종합 개선 실행 인계 — 진행 중 -현재 상태의 기준은 `plans/audit-2026-09-23/state.json`, 실제 문답과 이슈 본문은 `plans/audit-2026-09-23/issues/`다. 과거 검증 상세는 이슈별 validation JSON과 Git 기록을 읽는다. +기준 상태는 `plans/audit-2026-09-23/state.json`, 실제 전체 문답은 `issues/.md`다. 과거 상세 증거는 이슈별 validation JSON과 Git 기록에 있다. -## 사용자 요청과 제품 경계 +## 사용자 요청과 경계 -감사 59개 개선 항목과 기능 후보 5개, 총64개를 상세 GitHub 이슈로 만들고 우선순위/의존성 순서로 구현·검증한다. 항목마다 새 전담 서브에이전트가 grill-with-docs를 읽고 root에게 한 질문씩 보낸다. root가 사용자 대신 답하며 실제 전체 문답을 이슈에 넣고 게시 후 원격 본문과 대조한다. Android/iOS local-only 제품이며 서버·로그인·자동 cloud sync·remote analytics를 추가하지 않는다. +59개 개선과5개 기능 후보, 총64개를 상세 GitHub issue로 만들고 우선순위/의존성대로 구현·검증한다. 각 항목마다 새 전담 subagent가 grill-with-docs를 읽고 root에게 한 질문씩 보낸다. root가 사용자 대신 답하며 모든 실제 문답·정정을 이슈에 담고 게시 후 본문을 exact read-back한다. Android/iOS local-only: 서버/로그인/cloud sync/remote analytics 도입 없음. -실행하지 않은 수용 기준이 있으면 이슈는 열어 둔다. 테스트·빌드·PR·병합·실기기 전달·store 출시는 각각 다른 상태다. 사용자 추가 승인 없이 이미 확정한 범위의 구현, 커밋, Draft PR 갱신을 이어간다. +이미 승인된 구현·검증·커밋·PR 갱신은 계속한다. 자동검증·실기기·병합·store 출시 상태를 구분하며 미실행 수용 조건이 있는 이슈는 OPEN으로 둔다. 활성 goal은 미완료다. -## 현재 저장소와 이슈 +## 저장소와 게시 현황 -- 저장소 DevKor-github/OnTime-front, 브랜치 fix/audit-20260923-stabilization, Draft PR #585 (task attach 완료). -- 감사 baseline44067d7a. upstream PR590 반복 일정 main2b02fb77을 712b3708에서 통합했다. schema2/v1 migration, backupformat2/v1 호환, 반복 소유권·frozen·exclusions 및 가까운 미래60개 계약을 보존한다. -- 상세 이슈 **20/64개** 게시 및 exact read-back 완료, **44개 생성 전**. -- A01#583, A02#584, A03#586, A04#587, A05#588, A11#589, A12#591, A13#592, A14#593, A15#594, D03#595, U08#597, A06#598, A07#599, C01#600, A08#601, A09#602, D01#603, D02#604, D04#605. -- A01–A05/A11/A12/A13은 구현·자동 검증을 마쳤으나 기기 또는 후속 통합 조건이 남아 있다. U08은 홈 overflow 선행 패치만 구현했으며 전체 큰 글자 매트릭스는 미완료다. -- A14는 source24개 동결 및 root hash 대조를 완료했고 receipt·전용 UI 캡처·커밋 전달을 마무리 중이다. A15 원래 전담은 readonly 준비를 마쳤으며 A14 commit 이후 구현한다. -- A06/A07/C01/A08/A09/D01/D02/D03/A15의 실제 문답 및 상세 계획은 게시됐지만 구현 완료는 아니다. +- DevKor-github/OnTime-front, fix/audit-20260923-stabilization, Draft PR #585 (attach 완료). +- 감사 baseline44067d7a. main2b02fb77(PR590 recurring)을712b3708에서 통합했다. schema2/v1 migration, backupformat2/v1 호환, recurring ownership/frozen/exclusions, nearest60을 유지한다. +- 상세 이슈 **21/64개** 게시·exact read-back 완료, **43개 생성 전**. +- A01#583 A02#584 A03#586 A04#587 A05#588 A11#589 A12#591 A13#592 A14#593 A15#594 D03#595 U08#597 A06#598 A07#599 C01#600 A08#601 A09#602 D01#603 D02#604 D04#605 D05#606. +- A01–A05/A11/A12/A13 자동검증 통과, 기기/후속 통합 조건은 남음. U08 홈 overflow 선행 수정만 포함. A14 구현/CI 후 실제 registry ownership 보존 문제를 A15에서 후속 수정 중. A15는 아직 source freeze 전. +- D05까지 이슈별 설계가 확정됐으나 설계 게시와 구현 완료는 다르다. -## 최근 검증과 코드 기준 +## 최근 최종 검증 -- A12 b9f959db: remote707 tests/analyze, coverage87.11%, Android APK/manifest 통과. a12-ci-validation.json. 실제 OS cold-tap 미실행. -- U08 bde8b2de: 실제 폰트430×932 홈14px overflow 수정. focused18, analyzer 및 전용 PNG 검토. 전체 매트릭스 미완료. -- A13 59d92bdd: 정확 시각 권한·실제 timing receipt·선택적 안내 구현, 로컬734 tests. 이후 registry sanitizer가 timing을 누락하는 실제 결함을 발견해 edfff97c로 별도 수정, actual datasource/SharedPreferences mock 왕복6개 추가. -- A13 최종 후속 head e2fbdf0b: Flutter run35885445743 전체740/analyze·coverage87.20%(11319/12981), Android run35885445710 APK/manifest 통과. artifact10763011276, synthetic merge67efb127, APK SHA2560ba8caf4d01d1c043b7c237af1c67a42626bff73743d58d5a011e93c747a293a. a13-persistence-ci-validation.json에 기록. 이전734 결과는 sanitizer 결함의 검출 증거가 아니다. -- A14 최종 frozen source는 focused100, 전체772 tests/analyze, 실제 iOS26.5 SDK typecheck 및 AndroidSDK36/Flutter Kotlin compile을 통과했다. 중간 Flutter 명령 동시 실행의 ephemeral symlink 실패는 테스트 시작 전 환경 오류였고 같은 소스로 직렬 재시도해 통과했다. APK/Runner 전체 linking·실기기 전달과 구별한다. -- D02 결정으로 CONTEXT active key 용어를 정정하고 ADR0036을 e733c472에 기록했다. 정확히 한 active pair와 후보/retired의 한시 보존, manifest/store epoch와 DB runtime generation의 역할을 분리한다. 설계 확정이며 구현 완료가 아니다. +- A12 b9f959db: 원격707/analyze, coverage87.11%, Android APK/manifest 통과. a12-ci-validation.json. +- A13 sanitizer timing 보존 후 e2fbdf0b: 원격740/analyze,87.20%(11319/12981), Android 통과. artifact10763011276/APK0ba8caf4...; a13-persistence-ci-validation.json. 이전734는 sanitizer 결함 검출 증거가 아님. +- A14 282db21f source24 해시·focused100/full772/analyze/iOS26.5 SDK typecheck/AndroidSDK36 Kotlin compile 통과. artifacts/a14에는 실제 MyPage widget KO/EN PNG와 로그가 있음. OS 실기기 화면 아님. +- A14 b81259bf: Flutter35887794993 full772/analyze,87.44%(11487/13137), Android35887794956 통과. artifact10763053548/synthetic3ebcf192/APK7e88d688...; a14-ci-validation.json. +- A15 전 bebf2a31: Flutter35889125749 full772/analyze,87.33%(11472/13137), Android35889125735 통과. artifact10764089340/synthetic e53967c1/APKf8d95a14...; pre-a15-ci-validation.json. 동일 제품 소스여도 각 run의 coverage와 artifact identity를 섞지 않는다. +- A15 테스트: 결정적 snapshot barrier가 수정 전 실패. actual BackupService+Drift+owner restore/reset 경계33, UI/privacy/CRUD focused35 등 중간 결과 통과. 실제 CRUD/preparation/recurring nearest60 통합 후 최종 full/analyze 실행 중이며 최종 통과 숫자는 아직 없음. -## 에이전트와 다음 작업 +## 현재 에이전트 및 다음 작업 -1. A14 전담 `/root/grill_a14`의 최종 receipt/capture를 읽고 root가 #593 본문 exact read-back→명시적 source staging→commit/push→새 SHA CI를 기록한다. A14 동결 소스를 임의 수정하지 않는다. -2. `/root/grill_a15` 원래 전담에 A14 commit SHA를 전달하고 확정 #594 범위를 구현하도록 한다. 작은 단일 알림 writer, request cutoff별 완료와 trailing rerun, replacement quiesce/late native 반환 소유권을 지킨다. D03의 durable crash journal이나 D02 manifest를 여기서 완료했다고 주장하지 않는다. -3. 이후 D03→A06→A07→C01→A08→A09 순서와 README 전체 우선순위를 따른다. 독립적인 다음 grill은 병행 가능하다. -4. D04 신규 전담 생성은 agent thread limit으로 두 번 거절됐다. 다른 이슈 전담을 재사용하지 말고 슬롯/다음 실행에서 다시 생성한다. 기존 A15 전담 followup은 정상 접수됐다. 사용자 추가 승인/새 작업 요청은 필요 없다. -5. D04 사전 read-only 확인: upstream schema2와 실제 v1→v2 onUpgrade가 존재한다. recurring_migration_test는 현재v2 파일에서 반복 표면을 제거해 v1처럼 만든 파일을 재연다. 따라서 감사 baseline의 'migration 없음'을 그대로 재게시하지 말고 고정 과거 schema fixture·upgrade 실패 rollback/키 보존 등 실제 남은 범위를 새 전담 grill로 재평가한다. +1. `/root/grill_a15` 원래 전담이 source/test/A15.md/receipt를 소유해 구현 중. root는 tracking/GitHub/commit 소유. Flutter 명령을 병행하지 않는다. +2. A15는 단일 native owner·request cutoff/trailing drain·replacement generation을 구현한다. 취소 실패 최소 ownership을 실제 platform ID로 보존하고 repo upsert/dedup 왕복을 검사한다. reset marker 후 실패해도 DB/키를 지우거나 새 writer를 열지 않는다. DB start/finish 후 취소 오류를 durable commit 실패처럼 취급하지 않는다. actual BackupService/Reset/CRUD/Preparation/recurring 통합 증거 포함 필요. +3. A15 source freeze 이후 root hash/최종 tests/analyze/issue 전체 리뷰 반영을 확인하고 #594 read-back→명시적 staging→commit/push→새 SHA CI. A14 #593의 실제 ownership 후속 상태도 증거에 맞게 갱신한다. +4. 이후 D03 원래 전담 followup으로 durable crash journal/cleanup 구현. A15 인메모리 큐가 D03의 재시작 복구까지 완료했다고 주장하지 않는다. 전체 구현 순서는 README를 따른다. +5. D05 신규 전담 grill을 완료해 #606에 게시했다. 입력 암호문72MiB/평문64MiB/중첩32/일정100k/전체500k/정의별steps1000, 실제 bounded parsing·자료/그래프/시간대 검증·export 대칭·모바일 측정 계약이다. legacy duration에1440분을 일괄 강제하지 않고 과거 명시 offset 의미를 보존한다. D05 문답과 수용기준만 확정, 구현 전이다. +6. 다음 신규 전담은 A10. 이번 생성 시도는 agent thread limit으로 거절됐다. 다른 항목 전담을 재사용하지 말고 다음 실행에서 새 agent 생성. 사용자 승인 대기가 아니다. 그동안 A15 리뷰/검증을 계속한다. + +## 확정 복구 계약 + +D01#603: DB 비의존 shell/typed bootstrap recovery, 기존 파일이 있으면 키를 새로 만들지 않음. D02#604: 실제 암호화 후보 DB/key pair·manifest atomic selection·recovery journal, 새 pair read-back 후 old cleanup. CONTEXT active key와 ADR0036은 e733c472로 기록. A09#602 정상 복원은 기존 설치 키+DB transaction/runtime generation 정리이며 D02 manifest를 불필요하게 쓰지 않음. + +D04#605: 현재v1→v2가 이미 있다는 사실로 baseline 정정. DDL/version 원자성, 미래 schema 거부, 독립 역사 fixture, Android background SQLCipher loader/cipher_version guard 및 실제 모바일 검증을 요구한다. 현재 앱의 평문 유출을 재현했다는 뜻은 아니다. ## 환경과 주의 @@ -44,17 +50,3 @@ - 부팅된 iPhone17/iOS26.5 simulator OnTime Recurring QA 20260923 (C8BA159D-A97A-458B-A5FC-B04A8B6F2848)의 upstream QA 앱·데이터는 보존한다. 발견만으로 실제 전달·provider·양방향복원 검증을 통과 처리하지 않는다. - `/Users/ejunpark/.codex/worktrees/7b29/OnTime-front`는 다른 작업이다. 수정하지 않는다. - helper `/tmp/ontime-ci-audit.py`는 전체40자리 head SHA를 요구하며 해당 head의 Flutter와 Android 결과/작은 artifact identity를 기록한다. short SHA의 빈 결과를 근거로 쓰지 않는다. - -## A14 전달 완료와 A15 시작 - -A14는 282db21f5effa86eb2f82c81c247d57a0a6acfd0로 커밋했다. source24 해시 및 최종100/772/analyze/native SDK receipt와 전용 KO/EN PNG를 포함한다. #593 본문에 최종 구현/증거 링크를 넣고 exact read-back했다. A15 원래 전담에게 해당 SHA를 전달하고 구현을 허용했다. A14 원격 CI는 push 이후 새 head로 확인하며 아직 통과를 주장하지 않는다. - -원격 push head는 b81259bf66881289bf6bca9ac7cca2548de6a326이며 PR #585 본문을 A14 구현/증거 및 총19개 이슈에 맞춰 갱신하고 exact read-back했다. Flutter run35887794993, Android run35887794956은 실행 중이다. a14-ci-validation.json은 현재 중간 snapshot이며 최종 통과로 보고하지 않는다. 다음 실행은 이 두 run을 같은 전체 SHA로 확인한다. A15는 snapshot 이후 새 일정+두 번째 call barrier가 수정 전 실패함을 /tmp/a15-red.log로 확인하고 구현 중이다. - -## D04 게시 / A14 최종 CI / A15 리뷰 - -D04 #605 실제 네 문답 및 보충을 게시하고 exact read-back했다. 현재20/64,44개 미생성. 실제v1→v2가 있으므로 baseline을 정정했고 DDL/version 원자성, 미래 schema downgrade 거부, 과거 독립 fixture, Android background SQLCipher loader와 cipher_version guard, 실제 모바일 검증을 AC로 확정했다. 앱에서 평문 유출을 재현했다는 뜻은 아니다. - -A14 b81259bf의 Flutter35887794993 전체772/analyze·coverage87.44% 및 Android35887794956 APK/manifest가 모두 통과했다. artifact10763053548/synthetic merge3ebcf192/APK7e88d688...를 receipt에 기록했다. 다만 A15 리뷰에서 실제 registry의 scheduleId dedup이 복수 provider/platform-ID 취소 ownership을 잃을 수 있음을 발견해 후속 통합 미완료를 #593에 명시했다. A15가 실제 repo/datasource 왕복, 중복 ownership keeper·취소, reset intent 이후 실패 시 신규 writer 차단, DB 시작/완료 후 취소 오류의 별도 처리까지 수정 중이다. 이 검토 후 전체 A15 테스트는 아직 완료되지 않았다. - -D05 새 전담 생성은 이번 continuation에서도 thread limit으로 한 번 실패했다. D04 신규 생성은 성공했다. 다음 continuation에서 D05 신규 전담을 다시 생성하며 기존 전담을 다른 항목에 재사용하지 않는다. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 0e5a9f4a..7dbe5c6e 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -35,9 +35,10 @@ | D01 | [#603](https://github.com/DevKor-github/OnTime-front/issues/603) | 실제4문답·DB 비의존 시작 복구·키/파일 보존 기준 확정 | | D02 | [#604](https://github.com/DevKor-github/OnTime-front/issues/604) | 실제4문답·손상 DB 후보 검증/활성 쌍 교체·중단 복구 기준 확정 | | D04 | [#605](https://github.com/DevKor-github/OnTime-front/issues/605) | 실제4문답·migration 원자성/역사 schema/SQLCipher 확인·실패 보존 기준 확정 | +| D05 | [#606](https://github.com/DevKor-github/OnTime-front/issues/606) | 실제4문답·bounded stream/자료 한도·시간대/그래프 검증 기준 확정 | | U08 | [#597](https://github.com/DevKor-github/OnTime-front/issues/597) | 홈 overflow 선행 수정18 tests/analyze 통과, 전체 매트릭스·기기 검증 미완료 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **20/64개**를 게시하고 원격 본문 동일성을 확인했으며 **44개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14 로컬772 tests·native SDK 검증 후282db21f로 커밋했고 A15 전담 구현을 시작했다. D02까지 실제 문답과 복구 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **21/64개**를 게시하고 원격 본문 동일성을 확인했으며 **43개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14 로컬772 tests·native SDK 검증 후282db21f로 커밋했고 A15 전담 구현을 시작했다. D05까지 실제 문답과 백업·복구 검증 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/D05.md b/plans/audit-2026-09-23/issues/D05.md new file mode 100644 index 00000000..cb019f87 --- /dev/null +++ b/plans/audit-2026-09-23/issues/D05.md @@ -0,0 +1,200 @@ +# [P1][D05] 백업의 스트리밍 입력 한도와 전체 자료·시간대·준비 그래프 검증 + +상태: 전담 4문답과 모든 보충/정정 완료, 본문 동결. 구현/테스트 실행 전이며 수용 기준은 계획이다. +전담: `/root/grill_d05`; 적용 skill: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +조사 고정 commit: `bebf2a31a3a27fab93603492c0b15d452cd575ed`. + +## 문제와 영향 + +현재 파일 선택은 크기 확인 전에 전체 파일을 메모리에 읽는다. 암호문, 복호문 chunk 목록, 결합된 복호문, UTF-8 문자열, JSON 객체와 모델이 중첩해서 살아 있을 수 있다. 암호 header/frame별 상한은 있지만 파일·전체 복호문·레코드·문자열 한도가 없어 정상 암호를 가진 대형 파일 또는 잘못된 입력으로 모바일 프로세스가 종료될 수 있다. 인증만 통과한 파일이 제품의 관계·시간대·자료 표현 규칙까지 만족한다는 뜻은 아니다. 정상 데이터 복원과 손상 데이터 Recovery가 같은 검증기를 사용해야 한다. + +기존 암호화 suite와 비밀번호 규칙을 교체하는 작업이 아니다. Android/iOS local-only, envelope crypto version 1, payload format 1/2, schema 2 및 반복 일정·소유 preparation·exclusion 호환을 유지한다. 완전한 검증 전에는 활성 DB/키/runtime/delivery를 바꾸지 않는다. + +## 고정 소스 근거 + +| 근거 | 현재 확인한 사실 | +|---|---| +| [파일 선택과 preview](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/backup/backup_service.dart#L129-L164) | `readAsBytes()` 뒤 전체 복호문을 `utf8.decode`/`jsonDecode`한다. 실제 staging 완료 증거가 아니다. | +| [암호 frame 수/크기](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/backup/backup_crypto.dart#L12-L19), [복호화 결합](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/backup/backup_crypto.dart#L83-L142) | header 4096 bytes, frame 100000개, frame당 chunkSize+1024 제한은 있으나 누적 파일/평문 제한은 없다. `pullEx().toList()`와 평문 재결합이 존재한다. | +| [format 및 모델 생성](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/backup/backup_service.dart#L371-L452) | payload 1/2를 읽고 모델 전체를 만든 뒤 검증한다. 스트리밍 처리 없이 사후 count만 검사해서는 giant token 할당을 막지 못한다. | +| [자료와 preparation 검증](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/backup/backup_service.dart#L456-L514) | 일반 schedule zone은 nonempty와 offset 절댓값만 확인한다. preparation은 ID 중복과 next ID 존재만 확인하며 순환/다중 head/분리 그래프 검사가 없다. | +| [ordered의 보정](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/domain/entities/preparation_entity.dart#L19-L60), [decode 전에 ordered 호출](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/backup/backup_service.dart#L634-L650) | cycle을 방문 set으로 멈추고 남은 노드를 붙이는 UI용 순서화가 malformed backup의 그래프 유효성을 증명하지 못한다. | +| [일반 값 parser](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/backup/backup_service.dart#L663-L697) | 문자열 길이/정수 상한이 없고 날짜는 DateTime.tryParse만 사용한다. 날짜 overflow 자동 정규화를 별도로 거르지 않는다. | +| [CivilTimeResolver](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/time/civil_time_resolver.dart) | resolver는 알 수 없는 zone을 UTC로 대체하므로 엄격한 backup validator에서 단독 사용하면 안 된다. | +| [반복 일정 validator](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/backup/recurring_backup_data.dart), [RecurrenceEngine](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/domain/recurrence/recurrence_engine.dart) | 반복 정의·단계·segment·exclusion 참조 검사가 일부 존재하고 zone은 실제 lookup한다. 정의별 steps.where와 반복 JSON 전체 decode 등의 복잡도도 대형 입력에서 검증해야 한다. | +| [기존 UI 입력 한도](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/core/validation/local_input_limits.dart), [반복 step schema](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/lib/data/tables/recurring_schedule_tables.dart) | 새 입력 minute 상한 1440과 이름 30 제한이 존재한다. 반복 저장 단계는 0분을 허용하므로 신규 form 규칙을 과거 backup에 그대로 투영하지 않는다. | +| [현재 crypto tests](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/test/core/backup/backup_crypto_test.dart) | 작은 평문 round-trip, 잘못된 암호, 끝 byte 변경을 검사한다. 누적 바이트·token·multi-chunk 순서/잘림·메모리 경계를 증명하지 않는다. | + +## 기존 계약 + +[CONTEXT](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/CONTEXT.md)의 OnTime Backup, Backup Restore, Restore Preview, Backup Cutoff, Schedule Time Zone, Durable OnTime Data를 그대로 사용한다. 구현상의 byte/count 한도와 parser/state machine 이름은 새 제품 glossary가 아니다. + +- [ADR 0026](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/docs/adr/0026-use-a-cross-platform-backup-format.md): 플랫폼 중립 자료, source 설치 키 제외, staging 검증과 표현 불가 자료 사전 거절. +- [ADR 0028](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/docs/adr/0028-require-a-verified-restore-preview.md): 전체 인증·검증 뒤 preview와 별도 최종 확인. +- [ADR 0031](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/docs/adr/0031-use-argon2id-and-authenticated-stream-encryption.md): reviewed crypto, bounded chunks, final marker/순서/잘림 인증, whole-file buffering 배제. 파일 크기 검사만 추가하는 구현으로 이 약속을 충족하지 않는다. +- [ADR 0032](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/docs/adr/0032-normalize-and-bound-backup-passwords.md): 기존 NFC·15~128 Unicode code point·1024 UTF-8 bytes와 공백 보존을 그대로 유지한다. +- [ADR 0021](https://github.com/DevKor-github/OnTime-front/blob/bebf2a31a3a27fab93603492c0b15d452cd575ed/docs/adr/0021-anchor-schedules-to-a-named-time-zone.md): named zone·civil time·반복 시각 선택을 보존하고 과거 일정 변경 금지, 미래 tz rule 변경을 명시한다. +- A09 [#602](https://github.com/DevKor-github/OnTime-front/issues/602)는 정상 DB의 실제 암호화 staging/교체/runtime 무효화를 담당한다. D02 [#604](https://github.com/DevKor-github/OnTime-front/issues/604)는 같은 validator를 정상 DB 없이 사용하고 별도 후보 DB/key pair를 검증한다. D05는 두 흐름에 공통으로 적용된다. + +## 확정 결정: 총량과 스트리밍 + +| 항목 | 초기 수용 상한 | +|---|---:| +| 암호화 파일 실제 bytes | 72 MiB | +| 복호문 UTF-8 JSON 실제 bytes | 64 MiB | +| JSON object/array 중첩 | 32 | +| 일정 레코드 | 100,000 | +| 전체 레코드 | 500,000 | +| preparation 정의 하나의 단계 | 1,000 | + +각 상한을 동시에 만족해야 한다. 100,000개 일정의 모든 조합이 64 MiB에 들어간다는 보장이 아니다. 최소 10,000개 정상 일정 fixture를 검증한다. 상한은 성능 보장/SLA가 아니며 실제 모바일 메모리·시간 측정을 별도 증거로 남긴다. 수치를 바꾸려면 근거와 제품 결정을 기록하며 테스트만 맞추기 위한 은밀한 축소는 금지한다. + +사전 file length와 실제 stream 누적 bytes를 모두 검사한다. unknown/inaccurate length, 길이 검사 후 커진 provider 자료, chunk 경계에서 넘어가는 입력도 안전하게 실패해야 한다. final 인증 이전 provisional staging 적재는 사용자에게 노출하거나 활성화하지 않으며 final marker/EOF/전체 관계 검증 후에만 후보가 된다. giant frame/token을 할당한 후 제한하는 방식, 전체 JSON map 재결합, plaintext 임시 파일은 허용하지 않는다. 동일 정책을 export preflight/스트리밍 쓰기에 적용해 스스로 복원할 수 없는 새 파일을 성공으로 제공하거나 freshness를 갱신하지 않는다. 초과 자료를 자르거나 이력을 삭제하지 않고 지원 한도 오류와 원본 보존을 제공한다. 한도 오류는 암호 오류/파일 손상과 구분한다. + +## 확정 결정: 하위 자료 한도와 역사 호환 + +- ID/참조 ID는 UTF-8 512 bytes 이하이며 UUID 형식으로 새로 제한하지 않는다. object key도 UTF-8 512 bytes 이하이고 unknown field의 key/value도 byte/depth/token 검사 대상이다. +- 개별 문자열은 UTF-8 64 KiB 이하, note는 16,384 Unicode scalar와 UTF-8 64 KiB 중 먼저 닿는 상한을 적용한다. scalar 수와 UTF-16 code unit, UTF-8 bytes를 혼동하지 않는다. 기존 DB/domain 이름 길이는 해당 필드의 현행 의미대로 검사한다. backup 문자열을 임의 NFC 정규화/trim/절단하지 않는다. 비밀번호의 기존 NFC 규칙은 별도다. +- 전체 레코드 예산은 일정, place 입력, template, preparation 정의와 각 단계, recurring segment/exclusion, profile/preferences를 포함한다. 관계 검증에서 ID를 deduplicate하기 전에 원본 입력 항목을 계수하므로 중복 ID 반복으로 예산을 우회할 수 없다. staging preview의 고유 객체 count와 입력 자원 예산 count는 서로 다른 목적이다. +- duration은 원래 domain 상한이 명시된 필드에만 그 상한을 적용한다. **legacy 모든 duration을 1440분으로 제한하거나 총 준비 시간을 1,440,000분으로 새로 제한하지 않는다.** Q2의 초기 제안은 root가 정정했다. 0분이 합법인 legacy/recurring 자료를 신규 form의 최소 1분으로 거절하지 않는다. 음수, 분→microseconds 단위 곱셈 overflow, 합계 overflow, DB 정수 표현 불가, civil/instant 차감 후 year 1..9999 이탈을 모델 생성/산술 전에 검사한다. +- counter/revision은 정수 0..2^53−1 및 실제 저장·후속 증가가 안전한 범위다. `dataRevision + 1` 같은 후속 연산이 넘치는 후보를 ready로 제공하지 않는다. lateness의 기존 -1 등 sentinel은 일반 nonnegative 정책에 묶어 없애지 않는다. 점수의 onTime≤eligible 등 기존 불변식을 유지한다. +- 날짜는 year 1..9999이며 실제 calendar day/time 구성 요소를 strict round-trip 검증한다. 2월 30일·13월·24:xx·초 overflow를 DateTime의 자동 보정에 맡기지 않는다. civil 표현과 UTC instant 표현의 parser를 구분하여 offset 표기가 민간 시각을 조용히 이동시키지 않는다. 기존 format 1/2의 합법적인 ISO 표현은 golden fixture로 보존한다. + +## 확정 결정: timezone와 DST + +1. bundled IANA registry 및 지원 alias/UTC 존재를 직접 확인한 뒤 resolver를 호출한다. unknown zone은 명확한 검증 실패이며 UTC fallback 금지다. 네트워크 tz 조회를 추가하지 않는다. +2. strict civil 및 저장 offset 안전성을 먼저 검사한다. 과거/미래 판정에는 검증 시작 때 고정한 현재 UTC와 명시적 occurrence·완료 fact를 사용한다. 파일 cutoff가 인증됐다는 사실만으로 현재 시간이거나 믿을 수 있는 과거 분류 기준이 되지는 않는다. +3. 과거/완료 일정은 civil/zone/명시적 offset을 유지한다. 현 tzdb의 mismatch만으로 과거를 재작성하거나 백업 전체를 손상이라고 판단하지 않는다. offset 없는 legacy 과거 자료는 현재 규칙으로 유일한 occurrence를 해석할 수 있어도 원래 불확실성을 유지하며, 추정 offset을 원래 확정값으로 저장하지 않는다. +4. 미래의 유일 occurrence는 civil+zone을 보존하고 현재 규칙에 맞는 instant를 계산한다. 변경된 일정의 목록/개수와 영향은 verified preview 및 최종 확인에 포함한다. 이는 데이터를 조용히 정규화한 뒤 일반 성공으로 취급하는 동작이 아니다. +5. 미래 overlap에서 저장 선택이 후보에 대응하면 선택을 유지한다. absent/stale choice로 확정할 수 없거나 gap이면 ready/apply를 막고 명시적인 시간대 선택/규칙 확인 상태를 제공한다. A10의 사용자 선택/수정 계약으로 연결하며 임의 첫 offset/UTC/gap 앞으로 이동은 금지한다. +6. format 1/2에는 tzdb version이 없으므로 과거 규칙 변화와 원래 잘못된 자료를 완전히 판별할 수 있다고 주장하지 않는다. 반복 일정도 동일 원칙 아래 실제 materialized slot·override·exclusion·ordinal·segment 경계를 검증한다. 무한 series 전체를 펼치는 검증은 금지다. + +## 확정 결정: 관계와 연산 예산 + +- nonempty preparation은 원본 nextId 기준으로 head/tail 각 하나, indegree≤1, 모든 노드 정확히 한 번 도달하는 chain이어야 한다. self-link, cycle, 분리 chain, 여러 head/tail, 병합 edge, 다른 소유자 step 참조를 `ordered`/DAO 재연결 **전에** 검증한다. 빈 default 등 기존 도메인에서 합법인 상태는 신규 form 정책으로 거절하지 않는다. +- ID 유일성은 실제 table/owner namespace로 확인한다. 다른 table의 합법 동일 ID를 전역 금지하지 않으며 같은 table 안의 소유 경계 침범을 허용하지 않는다. duplicate JSON key를 마지막 값으로 덮지 않고 parser 단계에서 거절한다. 동일 place ID의 상충 내용, 참조 없는 template/definition/segment, 중복 occurrence slot, 잘못된 scope-owner 연결을 검사한다. 도메인상 합법인 삭제 template snapshot 및 보존용 orphan immutable definition은 임의 삭제/거절하지 않는다. +- 반복 정의/단계/소유자/segment/slot 인덱스를 한 번 만들고 재사용한다. 각 record에서 모든 정의나 원점부터의 slot을 반복 검색하는 n×m 검증을 피한다. 실제 staging의 foreign-key/uniqueness 제약 및 read-back은 순수 validator를 대체하지 않고 보완한다. +- 파일당 **2,000,000 logical work unit**을 초기 지원 예산으로 둔다. 구현 상수와 문서에서 단위를 고정한다: 레코드 admission/관계 검증을 위한 노드 방문 1회=1, graph/reference edge 검사 1회=1, recurrence 후보 civil slot 생성·검사 1회=1, zone offset 후보 round-trip 검사 1회=1. 재방문은 다시 계수하며 재사용 cache의 lookup도 해당 관계 확인 1회로 센다. 검증 함수 안에서 중첩 expansion을 새 독립 예산으로 초기화하지 않는다. 알고리즘 변경으로 단위를 바꾸면 근거와 fixture 영향을 기록한다. +- byte/token parsing 비용은 별도의 Q1 byte/depth/string 제한으로 다루며 1 byte=1 work unit으로 세지 않는다. work unit은 CPU 시간이나 모든 기계 instruction 수가 아니다. 단계별 계수/잔여 예산의 증감은 overflow-safe하게 처리하고 안전한 cancel/yield 경계를 둔다. index/cache 메모리도 레코드 한도와 모바일 peak memory 측정에 포함한다. +- 총량/연산 예산 초과는 지원 한도 오류다. 원본 손상/암호 오류로 보고하거나 데이터를 잘라 정상 backup으로 꾸미지 않는다. 최소 10,000 일정과 다중 segment 정상 fixture를 예산 안에서 통과시키고 결과를 기록한다. + +## 의존성 및 구현 순서 + +| 이슈/계약 | D05 통합 책임 | +|---|---| +| A09 #602 / D02 #604 | DB 비의존 공통 bounded input·crypto·format validator를 사용한다. 정상 restore의 DB transaction과 Recovery의 active pair 교체는 각 이슈가 소유한다. D05 실패 때 두 경로 모두 apply를 호출하지 않아야 한다. | +| C01 #600 / D01 #603 | 선택·검증·preview·확인·typed 실패/취소·cleanup pending을 실제 KO/EN UI에 연결한다. `unsupportedSize`, `invalidData`, `timeZoneResolutionRequired` 같은 상태의 실제 명칭은 기존 타입 설계에 맞추고 raw exception/path/사용자 자료를 노출하지 않는다. | +| A02 #584 / A03 #586 | mobile provider 입력과 성공 export receipt를 유지한다. 제한 검사 전 `readAsBytes`를 하는 adapter를 남겨 두면 미완료다. export의 native bytes-only bridge 때문에 전체 파일을 다시 올린다면 스트리밍/file-handle port까지 필요한 변경을 포함한다. | +| A08 #601 / A10 | 원자적 쓰기·소유 preparation/reader snapshot, 신규 입력 대 legacy restore 구분, 명시적 civil/zone/overlap 선택 계약을 공유한다. A10의 정확한 사용자 수정 경로와 통합되기 전 임의 timezone 보정으로 완료를 주장하지 않는다. | +| A15 #594 / D03 #595 | 기존 작업 소유권·quiesce·journal을 사용한다. parser 취소와 native 작업 종료를 혼동하지 않는다. validation 실패나 취소로 활성 runtime/delivery를 먼저 정리하지 않는다. | +| D04 #605 / D07 | 실제 schema/암호화 staging·마이그레이션·read-back 증거, format 1/2 및 template timestamp/recurring durable round-trip을 공유한다. D05의 불변식 검사로 데이터 누락을 감추지 않는다. | +| T03 / T06 / C03 | 실패 주입·대용량·메모리 측정 fixture/지표와 export snapshot N+1 성능을 공유한다. D05에서 bounded processing에 필요한 인덱싱은 수행하되 별개의 전면 성능 개편을 묶지 않는다. | + +구현 순서는 한도/typed failure와 순수 schema validators → bounded provider/secretstream/token reader → 실제 암호화 staging의 관계 검증/read-back → A09/D02/C01 통합 → export 대칭 규칙/round-trip → 실제 모바일 측정이다. payload version/crypto suite를 불필요하게 새로 만들지 않으며 library의 공개 스트리밍 API를 확인하고 final tag/EOF 의미를 테스트한다. generated Dart는 로컬 재생성하되 커밋하지 않는다. + +## 의미 있는 검증 행렬 + +아래는 실행할 계획이며 현재 통과 기록이 아니다. 성공 receipt나 호출 순서만 검사하지 않고 원본 durable rows/key/runtime/등록 상태 및 후보 read-back을 비교한다. 실패 시 raw secret이나 원문을 테스트 artifact에 남기지 않는다. + +| 시나리오 | 필요한 결과/증거 | +|---|---| +| 암호문/평문 cap−1, cap, cap+1 | 실제 bytes 누적의 경계 의미를 독립적으로 확인. over-limit에서는 다음 giant buffer를 만들기 전에 중단하며 지원 한도 오류. | +| length가 과대/과소/unknown, 사전 검사 뒤 증가 | provider metadata를 신뢰하지 않고 실제 누적으로 제한. length 조회 오류는 실제 지원 정책으로 처리하며 원본 변경 없음. | +| 크기 초과 파일 선택 | 거대한 파일 전체 read API 및 KDF/staging 시작 전에 가능한 사전 거절. 파일 선택 취소는 정상 cancellation. | +| chunk 분리 위치 변화 | 1-byte/split UTF-8/split JSON escape/split length/frame 경계에서도 동일 결과. 잘못된 UTF-8 또는 끊긴 Unicode escape를 silent replacement로 수용하지 않음. | +| 여러 crypto chunks | 실제 suite로 round-trip. header, 중간 frame, 순서 변경, 중복 frame, 누락 frame, final marker 제거, final 후 trailing data 모두 인증 실패/원본 보존. | +| unsafe crypto header | 기존 suite/KDF/header/frame 한도 검증이 비용 큰 KDF 또는 공격자 크기 할당보다 먼저 적용됨. 모르는 crypto/payload version을 현재 버전으로 강등하지 않음. | +| JSON depth 31/32/33 | 알려진 필드와 unknown subtree 모두 같은 경계 적용. 깊은 배열을 전체 재귀 decode한 뒤 검사하지 않음. | +| duplicate object key 및 giant key/value | 원래 중복을 일반 Map 마지막 값으로 덮기 전에 탐지. 최대 bytes/scalar를 token 읽는 중에 검사. escaped key도 의미상 동일 key인지 검사. | +| 이름/note/ID Unicode | ASCII, 한글, 4-byte scalar, 결합 문자, 공백을 포함한 실제 길이·bytes 경계. 문자열 내용의 trim/NFC/절단으로 과거 데이터를 바꾸지 않음. | +| schedule/전체 레코드/step count 경계 | raw 입력 항목 수를 세어 중복 ID로 예산 회피 불가. 고유 preview count와 처리 예산 count를 혼동하지 않음. | +| 0분/legacy UI보다 큰 합법 duration | 현 도메인이 허용하고 안전 산술/날짜 표현 가능한 기존 값은 유지. 신규 UI 1..1440 검증을 전체 decoder로 확대하지 않음. | +| 음수/단위 곱셈/합계 overflow | Duration 생성·DB bind·date subtract 이전에 검증 실패. 실제 field sentinel은 보존. | +| counter/revision 최대치와 후속 +1 | float/지수/부정확 정수 표현으로 잘못된 값 수용 금지. 저장 및 뒤따르는 revision 증가까지 안전함. | +| 날짜 overflow/범위 경계 | 2월30일, 13월, 24시, 잘못된 leap day, year0/10000, microseconds 및 timezone 표기. 보정된 다른 날짜로 복원되지 않음. | +| 정상 chain/빈 합법 preparation | 원본 의미·순서·ID·duration 보존. 한 노드, 정상 다중 노드, legacy 빈 default/법적으로 유효한 상태 검사. | +| cycle/self-link/분리 chain/merge/타 소유 참조 | 원본 graph 검사에서 거절. `ordered` 또는 DAO가 재연결한 뒤 합법처럼 보이는 경우까지 검증. | +| 중복 ID/상충 place/참조 누락 | DB 실제 key namespace와 domain owner 경계를 적용. 합법 shared immutable definition이나 삭제 template snapshot을 임의 삭제하지 않음. | +| 미지 IANA zone, 정상 alias, UTC | 실제 bundled lookup. 미지는 실패하고 현재 기기 zone/UTC fallback 없음. | +| DST gap/overlap | Seoul, New York, Berlin 및 30분 전환 Lord Howe 같은 fixture로 0/1/2 occurrence 의미 검증. overlap 올바른 선택 유지, 없거나 stale choice는 ready/apply 차단. | +| tzdb 변경과 과거 완료 | 명시적 과거 offset/civil/zone 유지. 동일 mismatch의 미래 유일 occurrence는 변경 영향 preview와 최종 확인. 과거 불확실 offset은 확정값을 합성하지 않음. | +| cutoff 조작/고정 now | 인증된 cutoff가 먼 과거/미래여도 그것만으로 이력 분류를 결정하지 않음. 한 검증 중 현재 시각 변화로 판정이 흔들리지 않음. | +| 반복 segment/slot/ordinal/override/exclusion | actual format2 생성 fixture로 소유권·구간·이전 삭제 exclusion·frozen occurrence 보존. count/until·무한 series 전체 expansion 금지. | +| 10,000 일정 정상 fixture | 다중 준비·template·recurring/exclusion을 포함해 전체 인증→staging→read-back→preview→왕복 성공. 일정 개수만 많은 얕은 mock을 대용량 증거로 쓰지 않음. | +| 총량에 가까운 유효 자료 | bytes/records/steps를 독립적으로 채운 fixture의 메모리/CPU 진행을 기록. 64 MiB JSON 전체 객체 재생성 없이 점진적 검증이 되는지 확인. | +| 연산 예산 경계 | 반복 ordinal 검사 및 그래프 검증이 total budget을 공유하고 초과 시 지원 한도 오류. 정상 fixture에 비례한 성능을 보이며 n×m 재검색을 숨기지 않음. | +| 파일/stream read 실패·disk 부족·staging write/read-back 실패 | ready preview 없음, 원본 DB/key/runtime/delivery 동일, 미완료 encrypted candidate만 ownership에 따라 정리. | +| 취소/화면 종료/중복 검증 | 실제 실행 소유권 종료 후 정리. 늦은 old attempt가 새 preview를 덮지 않음. 아직 쓰는 파일을 UI dispose가 지우지 않음. | +| staging cleanup 실패 | 데이터 무변경 상태와 cleanup pending을 구분하고 안전한 retry. active DB/key를 지워 해결하거나 성공으로 감추지 않음. | +| export 동일 policy | 한도 초과에서는 외부 성공 파일/freshness 없음. 허용된 신규 파일은 같은 validator로 완전 복원 가능. 기존 crypto header/payload1/2 호환 golden 검사. | +| 실제 mobile provider/자원 | Android→iOS, iOS→Android 파일 선택·검증·staging·cold read-back 및 10,000 fixture peak memory/시간 측정. 기기/OS/build SHA·fixture hash·데이터 수·bytes·측정 도구/범위를 기록. | + +메모리 측정에는 Dart heap만 아니라 crypto/KDF native memory, native file bridge, staging DB cache 등 가능한 프로세스 지표를 구분한다. 64 MiB KDF 자체 비용을 삭제해서 빨라진 듯 보고하지 않는다. 첫 실제 측정으로 기준선을 만들며 임의의 미측정 latency/RSS 합격 숫자를 발명하지 않는다. 노트북 단위 테스트는 실제 모바일 OOM/OS provider 종료를 입증하지 않는다. + +## 완료 체크리스트 + +- [ ] 파일 선택·복호화·token decode·staging 경로에서 전체 파일/평문/map 중복 buffering을 제거하고 누적 한도를 할당 전에 적용했다. +- [ ] 전체 인증/final/EOF·schema·관계·시간대 검증과 실제 staging read-back 뒤에만 verified preview를 제공한다. +- [ ] 위 한도와 duration 정정, 과거 offset 보존/미래 영향 설명을 A09와 D02 모두에 적용했다. +- [ ] 그래프/참조·반복 연산 예산 검사, 원본 보존 실패, 취소/cleanup pending에 재현 가능한 검증이 있다. +- [ ] 정상 export가 동일 정책으로 복원 가능하며 format1/2·schema migration·recurring/metadata 의미를 보존했다. +- [ ] KO/EN UI의 한도/불변식/인증/시간대 확인/후보 정리 실패를 사실대로 구별한다. +- [ ] 최소 10,000 일정 및 실제 모바일 provider/메모리/시간의 지정 증거가 있다. 미실행이면 이슈는 열어 둔다. +- [ ] 관련 source/test/analyze/CI 및 리뷰 후 커밋/PR에 결과와 남은 물리 기기 조건을 기록했다. 현재 문답 완료를 구현 완료로 처리하지 않는다. + +## 실제 grill 문답 + +아래는 전담과 root의 실제 순차 문답이다. root는 사용자 지시에 따라 대리 답한다. 구현과 GitHub 게시를 완료했다는 뜻이 아니다. + +### Q1 — 전체 크기와 bounded stream + +**전담 질문 원문** + +> D05 Q1 — bebf2a31에서 readAsBytes→복호문 전체 결합→jsonDecode가 겹치며 ADR 0031의 bounded stream 약속과 다릅니다. 총량 제한만 추가하고 이 경로를 유지하면 ADR를 충족하지 못합니다. 추천: 정상 A09·Recovery D02 공통 검증 입력을 bounded stream으로 바꾸고, 파일 제공자의 사전 length와 실제 누적 읽기/복호문 바이트를 모두 검사하며, 인증 final marker 이전에는 preview/활성 DB 변경을 금지합니다. 초기 제품 수용 한도는 암호화 파일 72 MiB, 복호문 UTF-8 JSON 64 MiB, JSON 중첩 32, 일정 100,000개, 모든 레코드 합계 500,000개, 준비 단계 1개 정의당 1,000개로 명시하고, 최소 10,000 일정 정상 fixture를 지원합니다. 제한은 자동 이력 삭제/자르기가 아니라 명확한 원본 보존 실패이며, 동일 한도를 export preflight에 적용해 새 앱이 스스로 복원 불가한 파일을 성공 저장하지 않도록 합니다. streaming parser가 전체 map을 다시 쌓거나 giant 문자열을 먼저 할당하지 않게 token/count 한도를 증가 단계에서 검사하고, 중간 산출물은 암호화 staging만 허용합니다. 이 정책/초기 숫자를 확정할까요? 숫자는 성능 보장 수치가 아니며 실제 모바일 peak memory/시간을 별도로 측정하고, 기존 유효 format 1/2 데이터가 넘으면 손실 변환 없이 명시적 지원 한도 오류와 현재 데이터 보존을 제공합니다. + +**root 답변 원문** + +> Q1을 확정합니다. 정상 A09와 손상 복구 D02가 같은 bounded input/인증·검증 경계를 사용하고, 사전 length와 실제 누적 암호문/복호문 크기를 모두 검사합니다. 초기 수용 한도는 암호문72MiB, UTF-8 복호문64MiB, 중첩32, 일정100,000개, 전체 레코드500,000개, 준비 정의당 단계1,000개로 명시합니다. 이는 각각의 상한이지 어떤100,000일정 조합도64MiB 안에 들어간다는 보장이 아니며 최소10,000일정의 정상 fixture를 실제로 검증합니다. export에도 같은 수용 규칙을 적용해 새로 내보낸 파일이 자체 정책 때문에 복원 불가하지 않게 합니다. 초과 입력을 자르거나 기존 이력을 삭제하지 않습니다. 인증 final marker 전 preview/active DB 변경 금지, 실제 streaming에서 giant token/frame 선할당과 전체 map 재결합을 막고 중간 파일은 암호화 staging만 허용합니다. 기존 유효 format1/2라도 한도 초과 시 명확한 지원 한도 오류와 원본 보존을 제공하고 이를 손상/암호 오류로 표시하지 않습니다. 실제 모바일 memory/시간 측정 결과에 따라 수치를 바꾸려면 근거와 변경 결정을 별도 기록하며, 테스트 통과를 위해 몰래 축소하지 않습니다. + +**추가 조사 전달 원문** + +> 추가 조사 사실: CivilTimeResolver.resolve는 알 수 없는 zone을 UTC로 대체하므로 D05는 직접 bundled tz.getLocation 확인 후 resolver를 호출해야 합니다. 일반 schedule은 zone 비어 있음과 ±24h offset만 검사하고, recurring만 zone 존재 검사가 있습니다. DateTime.tryParse는 날짜 overflow 정규화를 허용하고 preparation.ordered는 순환/분리 그래프를 고치는 듯 순서화해 현재 validator가 잘못된 그래프를 수용할 수 있습니다. Q1 답변 후 timezone/하위 자료 한도를 별도 질문으로 좁히겠습니다. + +### Q2 — 문자열·레코드·숫자·날짜 한도 + +**전담 질문 원문** + +> D05 Q2 — 현재 단순 String/int 수용 때문에 거대한 ID·note·분 값이 모델 생성/정렬/날짜 산술까지 갑니다. 추천 수용 규칙은 ID와 참조 ID 최대 512 UTF-8 bytes(기존 UUID만 강제하지 않음), 표시 이름·note는 기존 schema 표현 가능성을 보존하되 모든 개별 문자열 64 KiB UTF-8 이하, JSON object key 512 bytes 이하, 이름은 실제 해당 DB/도메인 한도(예: place/recurring step 30) 적용, 일반 note는 16,384 Unicode scalar와 64 KiB 중 먼저 닿는 한도로 합니다. UI 신규 입력의 1~1440분을 legacy restore에 그대로 강요하지 않고 저장된 0분을 허용하되 move/spare/profile spare/step은 각각 0~1440분, 준비 정의 합계도 checked arithmetic으로 검증합니다. 전체 preparation lead는 최대 1,440,000분(1,000×1,440)이며 일정 순간에서 차감 시 표현 범위를 벗어나면 실패합니다. 일반 카운터/revision은 0..2^53−1 및 실제 저장/증가 여유를 검사하고, lateness -1 sentinel 등 기존 특수값은 별도 계약으로 보존합니다. 모든 날짜는 year1..9999와 월/일/시/분/초의 strict round-trip으로 overflow 자동 보정을 금지합니다. native schema/과거 합법 데이터와 충돌하는 추가 길이 제한은 임의 정규화·절단 대신 명시적 미지원이며, unknown field도 depth/token/byte 제한을 우회하지 못하게 검사합니다. 이 하위 한도 정책을 확정할까요? record 총량에는 일정·고유 place·template·준비 정의·모든 준비 step·segment·exclusion과 profile/preferences를 포함하고 중복 ID로 count를 회피하지 못하게 입력 항목 수부터 셉니다. + +**root 답변 원문 — duration 상한 정정** + +> Q2는 문자열/ID/record count/strict 날짜/checked arithmetic 방향을 확정하되 duration 상한은 정정합니다. 512-byte ID/reference, 512-byte object key, 개별 문자열64KiB, note16,384 Unicode scalar와64KiB 중 먼저 닿는 한도, 실제 DB/domain 이름 제약을 적용합니다. 입력 항목부터 count하여 중복 ID로 총량을 피하지 못하게 하고 unknown field도 token/depth/bytes를 소비합니다. 날짜는 year1..9999와 strict round-trip, counter/revision은 정수0..2^53−1 및 저장/후속증가 여유, 기존 lateness sentinel 의미를 보존합니다. 다만 legacy restore에 각 duration0..1440분과 합계1,440,000분을 새로 강제하지 않습니다. 큰 duration 값 자체가 처리량을 늘리지 않는 경로라면 UI 한도보다 컸던 합법 데이터를 굳이 미지원으로 만들 이유가 약합니다. 실제 도메인에 원래1440분 상한이 명시된 필드만 따르고, 나머지는 음수 금지·Duration/DB 정수의 안전 표현·합계 checked arithmetic·civil/instant 차감 결과의 year1..9999 범위를 검증하세요. 생성 전에 단위 곱셈 overflow를 차단하고 불변식 위반과 자원 한도 초과 오류를 구별합니다. 기존 UI1..1440 규칙을 legacy backup decoder로 확대하지 않는 A08 결정과도 맞춰 주세요. 큰 문자열/파일/records 초과는 명시적 자원 한도 오류이며 자동 절단/정규화하지 않습니다. + +**root의 다음 질문 조사 지침 원문** + +> 다음 timezone 결정에는 ADR0021도 함께 대조해 주세요. tzdb 업데이트 후 미래 일정은 civil meaning을 유지하며 instant를 재계산·사용자에게 알리고, 과거 일정은 unchanged라는 계약입니다. 현재 tzdb offset과 다르다는 이유만으로 과거의 합법적 완료 일정/선택 offset을 임의 재작성하거나 모든 백업을 손상으로 처리하지 않도록, 실제 알려진 zone/불가능 civil time 검증과 규칙 버전 변경의 경계를 질문에서 구분해 주세요. + +### Q3 — 시간대 규칙 변경과 역사 보존 + +**전담 질문 원문** + +> D05 Q3 — timezone 검증은 unknown zone과 tzdb 변경을 구분합니다. 추천: bundled tz registry에 실제 존재하는 IANA ID/지원 alias(UTC 포함)를 확인한 후 civil resolver를 호출하고 UTC fallback은 금지합니다. strict civil 구성 요소와 저장 offset의 안전 범위를 먼저 검증합니다. 과거/완료 일정은 명시적 저장 offset·원래 civil 의미를 보존하고 현재 tzdb와 다르다는 사실만으로 손상/재작성하지 않습니다. 미래 일정은 현재 tzdb에서 유일한 occurrence이면 원래 civil+zone을 유지한 채 instant/offset 재계산 차이를 verified preview에 명시합니다. overlap은 저장된 선택 offset이 현재 후보에 대응할 때만 그 선택을 유지하고, absent/stale offset으로 선택을 확정할 수 없거나 현재 규칙상 gap이면 '시간대 선택/규칙 확인 필요'로 정상 ready preview와 apply를 막습니다. 임의 첫 offset 선택·UTC 대체·gap 앞으로 밀기는 금지하며 사용자 수정/선택은 A10의 명시적 시간대 UX 계약으로 연결합니다. format1/2에 tzdb version이 없으므로 현재 mismatch만으로 공격/원래 무효 데이터를 판별했다고 주장하지 않고, 과거 offset 없는 유일 occurrence는 기존 호환대로 현재 규칙으로 해석하되 역사적 instant 보존이 증명됐다고 표시하지 않습니다. 반복 규칙은 시작점뿐 아니라 실제 materialized slot/override/exclusion의 civil/ordinal/구간 관계를 검증하되 무한/수만 년 전체 expansion을 하지 않고 bounded 검증 계산을 사용합니다. 이 과거 보존·미래 변화 설명·불명확 선택 보류 정책을 확정할까요? + +**root 답변 원문** + +> Q3을 확정합니다. 알려진 IANA ID/지원 alias를 직접 확인하고 unknown zone을 UTC로 대체하지 않습니다. 과거/완료 일정은 저장된 civil/zone/명시적 offset을 보존하며 현 tzdb 불일치만으로 손상 처리하지 않습니다. 과거 offset 없는 유일 occurrence는 호환 해석이 가능해도 원본 필드의 불확실성을 보존하고, 역사 instant가 입증됐다고 하거나 추정 offset을 원래 확정값처럼 조용히 저장하지 않습니다. 미래의 유일 occurrence 재계산 차이는 verified preview의 영향 목록/개수와 최종 확인에 포함합니다. overlap 선택이 현재 후보에 대응하지 않거나 gap이면 ready/apply를 막고 A10의 명시적 선택/수정 경계로 연결합니다. 임의 첫 offset/UTC/gap 이동은 금지합니다. 과거·미래 판정에는 인증된 백업 cutoff만 믿지 말고 검증 시 고정한 현재 UTC 시각과 보존된 명시적 occurrence/완료 fact를 사용하고, offset 없는 legacy 판정의 불확실성을 표시하세요. 반복 자료는 bounded 관계 검증으로 slot/override/exclusion/ordinal을 확인하고 무한 expansion을 하지 않습니다. tzdb version이 없어 원래 무효와 규칙 변경을 판별할 수 없는 경우를 정확히 남깁니다. + + +### Q4 — 그래프·처리 예산·검증 증거 + +**전담 질문 원문** + +> D05 Q4 — 마지막으로 그래프·처리 예산·증거 경계를 제안합니다. preparation 원본 nextId를 ordered/DAO 재연결 전에 검증해 비어 있음이 합법인 legacy default 등을 보존하되 nonempty는 하나의 head/tail, 모든 노드 1회 도달, indegree≤1, cycle/self-link/분리 chain/타 소유 step 참조 거절로 합니다. ID 유일성은 실제 table/owner namespace에 맞추고 JSON duplicate key·동일 place ID의 상충 내용·dangling template/definition/segment·중복 occurrence slot·scope-owner 불일치를 staging 제약/read-back과 함께 검사합니다(삭제 template snapshot과 보존된 orphan immutable definition은 현 도메인상 합법이면 유지). 반복은 슬롯 검증당 원점부터 재확장하지 않고 index/cache를 사용하며 파일당 총 2,000,000 candidate/graph 검증 work unit이라는 초기 연산 상한과 취소/yield 경계를 둡니다. 예산 초과는 손상과 구별한 지원 한도 오류이고 10,000일정/다중segment 정상 fixture가 통과해야 합니다. 테스트는 실제 암호 다중 chunk, 한도−1/동일/+1, 거짓 file length, 느린/중단 stream, final/middle/tail 변조, Unicode byte/scalar·분 곱셈 overflow, 그래프/참조/날짜/TZ/DST, staging disk/쓰기/정리 실패, 취소/중복시도, Android↔iOS format1/2 정상 파일과 실제 모바일 peak memory/검증시간을 포함합니다. A09/D02 apply 호출0·활성 DB/키/runtime/알림 무변경을 실패마다 증명하고, desktop/mock 결과로 모바일 메모리/파일provider 완료를 대체하지 않습니다. 해당 정책을 확정하면 네 문답·모든 정정 원문을 D05.md에 보존하고 동결하겠습니다. + +**root 답변 원문** + +> Q4를 확정합니다. 원본 nextId 그래프를 ordered/DAO 정규화 전에 검증하고, 실제 namespace/소유권 및 합법 legacy empty·immutable orphan snapshot 예외를 보존합니다. duplicate JSON key와 상충 ID 내용도 조용히 last-write-wins로 처리하지 않습니다. 파일당 2,000,000 work unit은 초기 지원 예산으로 하되 candidate 방문/edge 검사 등 단위 정의를 문서와 구현에 고정하고 실제 입력에 비례해 누적해야 합니다. index/cache 자체의 메모리도 Q1 상한/모바일 측정에 포함합니다. 10,000일정과 다중 segment 정상 fixture를 통과시키며 overflow-safe 예산 계산, 취소/yield, 제한 오류와 손상 오류 구별을 유지합니다. 실제 암호·staging 실패 및 A09/D02 apply 호출0/활성 데이터 불변 검증과 모바일 교차 복원·peak memory/time을 완료 조건으로 확정합니다. 네 문답과 정정·추가 지시 원문 모두를 문서에 보존하고 동결해 주세요. 새 근거로 추가 질문이 필요하면 먼저 보내 주세요. + +## 전담 수행 범위 + +skill 직접 읽기, 현재 소스·CONTEXT·ADR·A09/D02/A08 계약 조사, root와 실제 네 문답 및 보충/정정, D05.md 작성만 수행했다. 소스·테스트·CONTEXT/ADR·공유 tracking·GitHub·commit은 변경하지 않았으며 테스트를 실행하지 않았다. 이번 결정은 기존 bounded stream/verified preview/named zone 계약을 구체화하므로 새로운 제품 용어나 암호 suite ADR를 만들지 않았다. root가 이후 GitHub 생성과 본문 exact read-back, 우선순위에 따른 구현/검증을 이어간다. diff --git a/plans/audit-2026-09-23/pre-a15-ci-validation.json b/plans/audit-2026-09-23/pre-a15-ci-validation.json new file mode 100644 index 00000000..2bdb7c84 --- /dev/null +++ b/plans/audit-2026-09-23/pre-a15-ci-validation.json @@ -0,0 +1,42 @@ +{ + "head_commit": "bebf2a31a3a27fab93603492c0b15d452cd575ed", + "runs": [ + { + "conclusion": "success", + "databaseId": 35889125749, + "event": "pull_request", + "headSha": "bebf2a31a3a27fab93603492c0b15d452cd575ed", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35889125749", + "workflowName": "Flutter Testing", + "tests_passed": 772, + "coverage": { + "percent": 87.33, + "lines_hit": 11472, + "lines_found": 13137 + } + }, + { + "conclusion": "success", + "databaseId": 35889125735, + "event": "pull_request", + "headSha": "bebf2a31a3a27fab93603492c0b15d452cd575ed", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35889125735", + "workflowName": "Android Notification Contract", + "artifact_id": 10764089340, + "identity": { + "commit": "e53967c17306e5347e287e666f783bf95fc5bc47", + "artifact": "build/app/outputs/flutter-apk/app-release.apk", + "sha256": "f8d95a141f8ba922df4e16150054d5200de66b36624e1673b42a35eaccc57f95", + "signing": "disposable CI validation key; not a store release", + "merged_manifests": [ + "build/app/intermediates/merged_manifest/release/outputReleaseAppLinkSettings/AndroidManifest.xml", + "build/app/intermediates/merged_manifest/release/processReleaseMainManifest/AndroidManifest.xml", + "build/app/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml" + ], + "device_delivery_verified": false + } + } + ] +} diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index d26d11a5..c9eb476a 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -561,16 +561,18 @@ "`backup_service.dart:101-103,415-472`; `backup_crypto.dart:18-19,111-135`", "읽기 전 파일 크기 제한, 레코드·텍스트·기간 상한, 실제 IANA zone/DST 검증, 준비 그래프·참조 무결성 검증. 초대형/잘린/이상 값 입력이 활성 DB를 바꾸지 않아야 함." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "ready", + "agent": "/root/grill_d05", + "issue_number": 606, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/606", "decisions_file": "plans/audit-2026-09-23/issues/D05.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 20 + "sequence": 20, + "readback_verified": true, + "body_sha256": "15cea8d115b001831a7fc9deb65e68fb9934879c2a361b50aa594fb06c4ad2ef" }, { "audit_id": "A10", From ecd04293363fa79bd9cd1b140169619dfc083b96 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 01:49:05 +0900 Subject: [PATCH 29/59] fix: serialize alarm reconciliation and data replacement ownership --- handoff/audit-2026-09-23-progress.md | 4 +- lib/core/backup/backup_service.dart | 6 +- .../database/local_data_operation_gate.dart | 8 +- .../database/local_data_reset_service.dart | 8 +- .../services/alarm_operation_coordinator.dart | 123 +++ .../services/alarm_registration_cleanup.dart | 55 ++ .../services/runtime_privacy_migration.dart | 25 +- .../alarm_registry_local_data_source.dart | 4 +- .../alarm_registry_repository_impl.dart | 11 +- lib/domain/entities/alarm_entities.dart | 12 + .../use-cases/cancel_all_alarms_use_case.dart | 53 +- .../cancel_schedule_alarm_use_case.dart | 49 +- .../create_custom_preparation_use_case.dart | 5 +- .../use-cases/reconcile_alarms_use_case.dart | 221 +++-- ...le_mutation_alarm_effects_coordinator.dart | 35 +- ...schedule_preparation_session_use_case.dart | 32 +- .../update_default_preparation_use_case.dart | 5 +- ...e_preparation_by_schedule_id_use_case.dart | 8 +- lib/presentation/app/bloc/auth/auth_bloc.dart | 2 +- .../app/cubit/alarm_gate_cubit.dart | 1 + lib/presentation/app/screens/app.dart | 2 +- lib/presentation/my_page/my_page_screen.dart | 26 +- plans/audit-2026-09-23/README.md | 4 +- plans/audit-2026-09-23/a15-validation.json | 238 +++++ .../artifacts/a15/analyzer.txt | 2 + .../artifacts/a15/automatic_mutation.txt | 7 + plans/audit-2026-09-23/artifacts/a15/full.txt | 800 +++++++++++++++++ .../artifacts/a15/generated_policy.txt | 1 + .../artifacts/a15/generation.txt | 21 + .../artifacts/a15/initial_full_failure.txt | 826 ++++++++++++++++++ .../artifacts/a15/local_boundary.txt | 1 + .../artifacts/a15/ownership_failure.txt | 76 ++ plans/audit-2026-09-23/artifacts/a15/red.txt | 15 + plans/audit-2026-09-23/issues/A14.md | 2 +- plans/audit-2026-09-23/issues/A15.md | 55 +- plans/audit-2026-09-23/state.json | 30 +- .../backup/backup_restore_picker_test.dart | 2 + test/core/backup/backup_service_test.dart | 2 + .../alarm_replacement_integration_test.dart | 195 +++++ .../alarm_registry_repository_impl_test.dart | 121 ++- ...alarm_reconciliation_concurrency_test.dart | 431 +++++++++ .../cancel_alarms_use_cases_test.dart | 24 +- .../reconcile_alarms_use_case_test.dart | 16 +- .../recurring_alarm_integration_test.dart | 190 ++++ ...tation_alarm_effects_coordinator_test.dart | 4 +- ...ule_preparation_session_use_case_test.dart | 49 ++ .../timed_preparation_use_cases_test.dart | 22 +- test/helpers/noop_alarm_cleanup.dart | 10 + test/helpers/noop_alarm_reconciliation.dart | 23 + .../my_page/my_data_restore_test.dart | 2 + .../my_page/my_page_screen_test.dart | 40 +- .../recurring_form_integration_test.dart | 8 +- 52 files changed, 3702 insertions(+), 210 deletions(-) create mode 100644 lib/core/services/alarm_operation_coordinator.dart create mode 100644 lib/core/services/alarm_registration_cleanup.dart create mode 100644 plans/audit-2026-09-23/a15-validation.json create mode 100644 plans/audit-2026-09-23/artifacts/a15/analyzer.txt create mode 100644 plans/audit-2026-09-23/artifacts/a15/automatic_mutation.txt create mode 100644 plans/audit-2026-09-23/artifacts/a15/full.txt create mode 100644 plans/audit-2026-09-23/artifacts/a15/generated_policy.txt create mode 100644 plans/audit-2026-09-23/artifacts/a15/generation.txt create mode 100644 plans/audit-2026-09-23/artifacts/a15/initial_full_failure.txt create mode 100644 plans/audit-2026-09-23/artifacts/a15/local_boundary.txt create mode 100644 plans/audit-2026-09-23/artifacts/a15/ownership_failure.txt create mode 100644 plans/audit-2026-09-23/artifacts/a15/red.txt create mode 100644 test/core/database/alarm_replacement_integration_test.dart create mode 100644 test/domain/use-cases/alarm_reconciliation_concurrency_test.dart create mode 100644 test/helpers/noop_alarm_cleanup.dart create mode 100644 test/helpers/noop_alarm_reconciliation.dart diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 4435ec91..e4c06d3b 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -31,9 +31,9 @@ 1. `/root/grill_a15` 원래 전담이 source/test/A15.md/receipt를 소유해 구현 중. root는 tracking/GitHub/commit 소유. Flutter 명령을 병행하지 않는다. 2. A15는 단일 native owner·request cutoff/trailing drain·replacement generation을 구현한다. 취소 실패 최소 ownership을 실제 platform ID로 보존하고 repo upsert/dedup 왕복을 검사한다. reset marker 후 실패해도 DB/키를 지우거나 새 writer를 열지 않는다. DB start/finish 후 취소 오류를 durable commit 실패처럼 취급하지 않는다. actual BackupService/Reset/CRUD/Preparation/recurring 통합 증거 포함 필요. 3. A15 source freeze 이후 root hash/최종 tests/analyze/issue 전체 리뷰 반영을 확인하고 #594 read-back→명시적 staging→commit/push→새 SHA CI. A14 #593의 실제 ownership 후속 상태도 증거에 맞게 갱신한다. -4. 이후 D03 원래 전담 followup으로 durable crash journal/cleanup 구현. A15 인메모리 큐가 D03의 재시작 복구까지 완료했다고 주장하지 않는다. 전체 구현 순서는 README를 따른다. +4. D03 원래 전담 `/root/grill_d03` followup이 접수되어 읽기 전용 구현 준비 중이다. root가 A15 commit SHA를 전달하기 전 소스/테스트/문서 편집 및 Flutter 명령은 금지했다. 이후 durable crash journal/cleanup을 구현한다. A15 인메모리 큐가 D03의 재시작 복구까지 완료했다고 주장하지 않는다. 전체 구현 순서는 README를 따른다. 5. D05 신규 전담 grill을 완료해 #606에 게시했다. 입력 암호문72MiB/평문64MiB/중첩32/일정100k/전체500k/정의별steps1000, 실제 bounded parsing·자료/그래프/시간대 검증·export 대칭·모바일 측정 계약이다. legacy duration에1440분을 일괄 강제하지 않고 과거 명시 offset 의미를 보존한다. D05 문답과 수용기준만 확정, 구현 전이다. -6. 다음 신규 전담은 A10. 이번 생성 시도는 agent thread limit으로 거절됐다. 다른 항목 전담을 재사용하지 말고 다음 실행에서 새 agent 생성. 사용자 승인 대기가 아니다. 그동안 A15 리뷰/검증을 계속한다. +6. A10 사전 read-only 확인: ScheduleBloc:244/835, Home timer:39, TodaysScheduleTile:158, adjacent usecase:54/73에 scheduleTime 직접 비교가 남아 있다. occurrenceInstantUtc의 offset-null fallback은 device-local toUtc이고 CivilTimeResolver unknown zone은 UTC fallback이다. 이 사실만으로 모든 civil-date 조회를 instant 비교로 일괄 치환하지 말고 A10 전담이 calendar bucket/commitment 의미를 구분해야 한다. 다음 신규 전담은 A10. 이번 생성 시도는 agent thread limit으로 거절됐다. 다른 항목 전담을 재사용하지 말고 다음 실행에서 새 agent 생성. 사용자 승인 대기가 아니다. 그동안 A15 리뷰/검증을 계속한다. ## 확정 복구 계약 diff --git a/lib/core/backup/backup_service.dart b/lib/core/backup/backup_service.dart index 6ab978ff..975a6977 100644 --- a/lib/core/backup/backup_service.dart +++ b/lib/core/backup/backup_service.dart @@ -1,5 +1,6 @@ import 'package:on_time_front/core/backup/recurring_backup_data.dart'; import 'dart:convert'; +import 'package:on_time_front/domain/use-cases/cancel_all_alarms_use_case.dart'; import 'package:drift/drift.dart'; import 'package:file_selector/file_selector.dart'; @@ -65,7 +66,8 @@ class BackupRestoreCandidate { class BackupService { BackupService( this._database, - this._metadataProvider, { + this._metadataProvider, + this._cancelAllAlarms, { @ignoreParam BackupCrypto? crypto, @ignoreParam BackupFileExportPort? exportPort, @ignoreParam LocalDataOperationGate? operationGate, @@ -83,6 +85,7 @@ class BackupService { ); final AppDatabase _database; + final CancelAllAlarmsUseCase _cancelAllAlarms; final AppMetadataProvider _metadataProvider; final BackupCrypto _crypto; final BackupFileExportPort _exportPort; @@ -172,6 +175,7 @@ class BackupService { _operationGate.run(() => _applyRestore(candidate), replacesData: true); Future _applyRestore(BackupRestoreCandidate candidate) async { + await _cancelAllAlarms.forDataReplacement(); final data = candidate._data; final profile = data.profile.valueOrNull!; await _database.transaction(() async { diff --git a/lib/core/database/local_data_operation_gate.dart b/lib/core/database/local_data_operation_gate.dart index 2c48daf3..7724878c 100644 --- a/lib/core/database/local_data_operation_gate.dart +++ b/lib/core/database/local_data_operation_gate.dart @@ -7,9 +7,12 @@ final class LocalDataOperationGate extends ChangeNotifier { bool _busy = false; bool _unavailable = false; + bool _replacingData = false; int _generation = 0; int get generation => _generation; + bool get isReplacingData => _replacingData; + bool get isInvalidated => _unavailable; bool get isAvailable => !_busy && !_unavailable; Future run( @@ -19,17 +22,20 @@ final class LocalDataOperationGate extends ChangeNotifier { if (_busy) throw const LocalDataOperationBusy(); if (_unavailable) throw const LocalDataUnavailable(); _busy = true; + _replacingData = replacesData; if (replacesData) _generation++; notifyListeners(); try { return await action(); } finally { _busy = false; + _replacingData = false; notifyListeners(); } } - /// Reset closes the active database; it cannot be used again before restart. + /// Reset intent makes the installation unavailable until restart, including + /// when cancellation fails before the old database can safely be closed. void invalidate() { _generation++; _unavailable = true; diff --git a/lib/core/database/local_data_reset_service.dart b/lib/core/database/local_data_reset_service.dart index 8f1deb77..c6801736 100644 --- a/lib/core/database/local_data_reset_service.dart +++ b/lib/core/database/local_data_reset_service.dart @@ -26,10 +26,12 @@ class LocalDataResetService { Future _reset() async { await LocalDataLifecycle.markResetPending(); - await _cancelAllAlarms().catchError((_) {}); - await NotificationService.instance.cancelAll().catchError((_) {}); - await _database.close(); + // The durable reset intent is now irrevocable in this process. Even if + // cleanup fails, do not reopen normal writers; bootstrap resumes reset. _operationGate.invalidate(); + await _cancelAllAlarms.forDataReplacement(); + await NotificationService.instance.cancelAll(); + await _database.close(); await deleteLocalDatabaseFiles(includeLegacy: true); await (await SharedPreferences.getInstance()).clear(); await _keyStore.delete(); diff --git a/lib/core/services/alarm_operation_coordinator.dart b/lib/core/services/alarm_operation_coordinator.dart new file mode 100644 index 00000000..6d898a20 --- /dev/null +++ b/lib/core/services/alarm_operation_coordinator.dart @@ -0,0 +1,123 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/domain/repositories/alarm_registry_repository.dart'; + +/// One owner for schedule delivery effects. A timeout never releases this owner: +/// only completion of the underlying platform Future releases its queue slot. +final class AlarmOperationCoordinator extends ChangeNotifier { + AlarmOperationCoordinator(this.gate) { + gate.addListener(notifyListeners); + } + + static final shared = AlarmOperationCoordinator( + LocalDataOperationGate.shared, + ); + final LocalDataOperationGate gate; + Future _tail = Future.value(); + final _unpersisted = Expando>(); + + int get generation => gate.generation; + bool get canSchedule => !gate.isReplacingData && !gate.isInvalidated; + + AlarmOperationLease capture() { + final lease = AlarmOperationLease._(this, generation); + lease.check(); + return lease; + } + + Future run(AlarmOperationLease lease, Future Function() action) => + _enqueue(() async { + lease.check(); + return action(); + }); + + /// Called inside the replacement gate. This does not await gate release or + /// enqueue a normal reconciliation, which would deadlock the replacement. + Future cleanup(Future Function() action) => _enqueue(action); + + Future _enqueue(Future Function() action) { + final result = _tail.then((_) => action()); + _tail = result.then((_) {}, onError: (Object _, StackTrace __) {}); + return result; + } + + Future> loadRecords( + AlarmRegistryRepository registry, + ) async { + final stored = await registry.loadAll(); + return { + for (final record in stored) alarmOwnershipKey(record): record, + ...?_unpersisted[registry], + }.values.toList(); + } + + Future remember( + AlarmRegistryRepository registry, + List records, + ) async { + final memory = _unpersisted[registry] ??= {}; + for (final record in records) { + memory[alarmOwnershipKey(record)] = ownershipOnly(record); + } + // Keep memory until the final write succeeds, including when disk fails. + await registry.replaceAll(await loadRecords(registry)); + } + + Future replaceRecords( + AlarmRegistryRepository registry, + List records, + ) async { + try { + await registry.replaceAll(records); + _unpersisted[registry] = null; + } catch (_) { + final memory = _unpersisted[registry] ??= {}; + for (final record in records) { + memory[alarmOwnershipKey(record)] = ownershipOnly(record); + } + rethrow; + } + } + + static ScheduledAlarmRecord ownershipOnly(ScheduledAlarmRecord record) => + ScheduledAlarmRecord( + scheduleId: record.scheduleId, + provider: record.provider, + nativeAlarmId: record.nativeAlarmId, + fallbackNotificationId: record.fallbackNotificationId, + alarmTime: record.alarmTime, + preparationStartTime: record.preparationStartTime, + scheduleFingerprint: '', + scheduleTitle: 'OnTime', + payload: const {}, + notificationTiming: record.notificationTiming, + cancellationPending: true, + ); + + @override + void dispose() { + gate.removeListener(notifyListeners); + super.dispose(); + } +} + +final class AlarmOperationLease { + AlarmOperationLease._(this._owner, this.generation); + final AlarmOperationCoordinator _owner; + final int generation; + bool get isCurrent => _owner.generation == generation && _owner.canSchedule; + void check() { + if (!isCurrent) throw const AlarmOperationInvalidated(); + } +} + +final class AlarmOperationInvalidated implements Exception { + const AlarmOperationInvalidated(); +} + +final class AlarmCleanupIncomplete implements Exception { + const AlarmCleanupIncomplete(); +} diff --git a/lib/core/services/alarm_registration_cleanup.dart b/lib/core/services/alarm_registration_cleanup.dart new file mode 100644 index 00000000..58aa69b3 --- /dev/null +++ b/lib/core/services/alarm_registration_cleanup.dart @@ -0,0 +1,55 @@ +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; +import 'package:on_time_front/core/services/fallback_alarm_notification_service.dart'; +import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/domain/repositories/alarm_registry_repository.dart'; + +/// Invoked only by the schedule-delivery owner, including replacement cleanup. +/// Provider cancellation already performs its platform-specific read-back. +final class AlarmRegistrationCleanup { + AlarmRegistrationCleanup( + this.registry, + this.scheduler, + this.fallback, + this.operations, + ); + final AlarmRegistryRepository registry; + final AlarmSchedulerService scheduler; + final FallbackAlarmNotificationService fallback; + final AlarmOperationCoordinator operations; + + Future> cancelRecords( + List records, + ) async { + if (records.isNotEmpty) await operations.remember(registry, records); + final failed = []; + for (final record in records) { + try { + if (record.provider == AlarmProvider.localNotification) { + await fallback.cancelFallbackAlarm(record); + } else if (record.provider != AlarmProvider.none) { + await scheduler.cancelNativeAlarm(record); + } + } catch (_) { + failed.add(AlarmOperationCoordinator.ownershipOnly(record)); + } + } + return failed; + } + + Future cancelMatching({String? scheduleId}) async { + final stored = await operations.loadRecords(registry); + final targets = stored + .where( + (record) => scheduleId == null || record.scheduleId == scheduleId, + ) + .toList(); + final failed = await cancelRecords(targets); + await operations.replaceRecords(registry, [ + if (scheduleId != null) + ...stored.where((record) => record.scheduleId != scheduleId), + ...failed, + ]); + if (failed.isNotEmpty) throw const AlarmCleanupIncomplete(); + } +} diff --git a/lib/core/services/runtime_privacy_migration.dart b/lib/core/services/runtime_privacy_migration.dart index dc0f3eec..ad44b5db 100644 --- a/lib/core/services/runtime_privacy_migration.dart +++ b/lib/core/services/runtime_privacy_migration.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:flutter/services.dart'; import 'package:on_time_front/core/database/bootstrap_privacy_boundary.dart'; import 'package:injectable/injectable.dart'; @@ -23,7 +24,10 @@ class RuntimePrivacyMigration { this.snapshots, this.registry, { @ignoreParam Future Function()? cleanPlatform, - }) : _cleanPlatform = cleanPlatform; + @ignoreParam AlarmOperationCoordinator? operations, + }) : _cleanPlatform = cleanPlatform, + _operations = operations ?? AlarmOperationCoordinator.shared; + final AlarmOperationCoordinator _operations; final Future Function()? _cleanPlatform; final ScheduleRepository schedules; final PreparationLocalDataSource preparations; @@ -33,7 +37,12 @@ class RuntimePrivacyMigration { static const _earlyPrefix = 'early_start_session_'; static const _native = MethodChannel('on_time_front/native_alarm'); - Future run() async { + Future run() { + final lease = _operations.capture(); + return _operations.run(lease, _run); + } + + Future _run() async { final prefs = await SharedPreferences.getInstance(); for (final key in prefs.getKeys().where((key) => key.startsWith(_prefix)).toList()) { @@ -69,7 +78,7 @@ class RuntimePrivacyMigration { Error.throwWithStackTrace(error, stack); }, cleanup: () => - clearLegacyWithoutDatabase(cleanupPlatform: _cleanPlatform), + _clearLegacyWithoutDatabase(cleanupPlatform: _cleanPlatform), ); } } @@ -107,6 +116,16 @@ class RuntimePrivacyMigration { static Future clearLegacyWithoutDatabase({ Future Function()? cleanupPlatform, + }) { + final owner = AlarmOperationCoordinator.shared; + return owner.run( + owner.capture(), + () => _clearLegacyWithoutDatabase(cleanupPlatform: cleanupPlatform), + ); + } + + static Future _clearLegacyWithoutDatabase({ + Future Function()? cleanupPlatform, }) async { final prefs = await SharedPreferences.getInstance(); // With no authoritative DB there is no way to validate arbitrary runtime diff --git a/lib/data/data_sources/alarm_registry_local_data_source.dart b/lib/data/data_sources/alarm_registry_local_data_source.dart index da023b87..e8966c98 100644 --- a/lib/data/data_sources/alarm_registry_local_data_source.dart +++ b/lib/data/data_sources/alarm_registry_local_data_source.dart @@ -49,7 +49,9 @@ class AlarmRegistryLocalDataSourceImpl implements AlarmRegistryLocalDataSource { contentDigest: current ? record.contentDigest : null, contentVersion: current ? record.contentVersion : null, contentLanguageCode: current ? record.contentLanguageCode : null, - notificationTiming: current ? record.notificationTiming : null, + // The enum is a scheduling receipt, not private content. Keep it even + // on a minimal cancellation tombstone with no fingerprint/content. + notificationTiming: record.notificationTiming, cancellationPending: record.cancellationPending || !current, notificationContent: current ? record.notificationContent : null, ); diff --git a/lib/data/repositories/alarm_registry_repository_impl.dart b/lib/data/repositories/alarm_registry_repository_impl.dart index 75fa569a..3d25ac93 100644 --- a/lib/data/repositories/alarm_registry_repository_impl.dart +++ b/lib/data/repositories/alarm_registry_repository_impl.dart @@ -19,7 +19,10 @@ class AlarmRegistryRepositoryImpl implements AlarmRegistryRepository { final records = await loadAll(); final nextRecords = records - .where((existing) => existing.scheduleId != record.scheduleId) + .where( + (existing) => + alarmOwnershipKey(existing) != alarmOwnershipKey(record), + ) .toList() ..add(record); await replaceAll(nextRecords); @@ -40,10 +43,10 @@ class AlarmRegistryRepositoryImpl implements AlarmRegistryRepository { @override Future replaceAll(List records) { - final byScheduleId = {}; + final byOwnership = {}; for (final record in records) { - byScheduleId[record.scheduleId] = record; + byOwnership[alarmOwnershipKey(record)] = record; } - return localDataSource.replaceAll(byScheduleId.values.toList()); + return localDataSource.replaceAll(byOwnership.values.toList()); } } diff --git a/lib/domain/entities/alarm_entities.dart b/lib/domain/entities/alarm_entities.dart index c2e0c891..3a2f7831 100644 --- a/lib/domain/entities/alarm_entities.dart +++ b/lib/domain/entities/alarm_entities.dart @@ -522,3 +522,15 @@ ScheduledAlarmRecord buildScheduledAlarmRecord( }, ); } + +/// Identity of the actual owned OS request, not its database Schedule label. +String alarmOwnershipKey(ScheduledAlarmRecord record) { + final Object identity = switch (record.provider) { + AlarmProvider.localNotification => + record.fallbackNotificationId ?? stableAlarmId(record.scheduleId), + AlarmProvider.androidAlarmManager => + record.nativeAlarmId ?? stableAlarmId(record.scheduleId), + AlarmProvider.iosAlarmKit || AlarmProvider.none => record.scheduleId, + }; + return '${record.provider.name}:$identity'; +} diff --git a/lib/domain/use-cases/cancel_all_alarms_use_case.dart b/lib/domain/use-cases/cancel_all_alarms_use_case.dart index 340d4850..5ac526a3 100644 --- a/lib/domain/use-cases/cancel_all_alarms_use_case.dart +++ b/lib/domain/use-cases/cancel_all_alarms_use_case.dart @@ -1,43 +1,36 @@ import 'package:injectable/injectable.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; import 'package:on_time_front/core/services/fallback_alarm_notification_service.dart'; -import 'package:on_time_front/domain/entities/alarm_entities.dart'; import 'package:on_time_front/domain/repositories/alarm_registry_repository.dart'; @Injectable() class CancelAllAlarmsUseCase { - final AlarmRegistryRepository _registryRepository; - final AlarmSchedulerService _schedulerService; - final FallbackAlarmNotificationService _fallbackNotificationService; - CancelAllAlarmsUseCase( - this._registryRepository, - this._schedulerService, - this._fallbackNotificationService, - ); + AlarmRegistryRepository registry, + AlarmSchedulerService scheduler, + FallbackAlarmNotificationService fallback, { + @ignoreParam AlarmOperationCoordinator? operations, + }) : _operations = operations ?? AlarmOperationCoordinator.shared, + _cleanup = AlarmRegistrationCleanup( + registry, + scheduler, + fallback, + operations ?? AlarmOperationCoordinator.shared, + ); - Future call() async { - final records = await _registryRepository.loadAll(); - await cancelRecords(records); - await _registryRepository.deleteAll(); - } + final AlarmOperationCoordinator _operations; + final AlarmRegistrationCleanup _cleanup; - Future cancelRecords(List records) async { - for (final record in records) { - await _cancelRecord(record); - } + Future call() async { + final lease = _operations.capture(); + await _operations.run(lease, _cleanup.cancelMatching); + lease.check(); } - Future _cancelRecord(ScheduledAlarmRecord record) async { - try { - if (record.provider == AlarmProvider.localNotification) { - await _fallbackNotificationService.cancelFallbackAlarm(record); - } else if (record.provider != AlarmProvider.none) { - await _schedulerService.cancelNativeAlarm(record); - } - } catch (_) { - // Best-effort cancellation: stale registry cleanup should not be blocked - // by a platform channel or notification plugin failure. - } - } + /// The caller already owns the data replacement gate. Wait for actual old + /// platform Futures, then cancel; never wait for normal drain/gate release. + Future forDataReplacement() => + _operations.cleanup(_cleanup.cancelMatching); } diff --git a/lib/domain/use-cases/cancel_schedule_alarm_use_case.dart b/lib/domain/use-cases/cancel_schedule_alarm_use_case.dart index e3697604..3920b85a 100644 --- a/lib/domain/use-cases/cancel_schedule_alarm_use_case.dart +++ b/lib/domain/use-cases/cancel_schedule_alarm_use_case.dart @@ -1,39 +1,34 @@ import 'package:injectable/injectable.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; import 'package:on_time_front/core/services/fallback_alarm_notification_service.dart'; -import 'package:on_time_front/domain/entities/alarm_entities.dart'; import 'package:on_time_front/domain/repositories/alarm_registry_repository.dart'; @Injectable() class CancelScheduleAlarmUseCase { - final AlarmRegistryRepository _registryRepository; - final AlarmSchedulerService _schedulerService; - final FallbackAlarmNotificationService _fallbackNotificationService; - CancelScheduleAlarmUseCase( - this._registryRepository, - this._schedulerService, - this._fallbackNotificationService, - ); - - Future call(String scheduleId) async { - final records = await _registryRepository.loadAll(); - final matchingRecords = records - .where((record) => record.scheduleId == scheduleId) - .toList(growable: false); + AlarmRegistryRepository registry, + AlarmSchedulerService scheduler, + FallbackAlarmNotificationService fallback, { + @ignoreParam AlarmOperationCoordinator? operations, + }) : _operations = operations ?? AlarmOperationCoordinator.shared, + _cleanup = AlarmRegistrationCleanup( + registry, + scheduler, + fallback, + operations ?? AlarmOperationCoordinator.shared, + ); - for (final record in matchingRecords) { - try { - if (record.provider == AlarmProvider.localNotification) { - await _fallbackNotificationService.cancelFallbackAlarm(record); - } else if (record.provider != AlarmProvider.none) { - await _schedulerService.cancelNativeAlarm(record); - } - } catch (_) { - // Keep cleanup idempotent when the platform has already removed it. - } - } + final AlarmOperationCoordinator _operations; + final AlarmRegistrationCleanup _cleanup; - await _registryRepository.deleteByScheduleId(scheduleId); + Future call(String scheduleId) async { + final lease = _operations.capture(); + await _operations.run( + lease, + () => _cleanup.cancelMatching(scheduleId: scheduleId), + ); + lease.check(); } } diff --git a/lib/domain/use-cases/create_custom_preparation_use_case.dart b/lib/domain/use-cases/create_custom_preparation_use_case.dart index b8012ecd..5547be5f 100644 --- a/lib/domain/use-cases/create_custom_preparation_use_case.dart +++ b/lib/domain/use-cases/create_custom_preparation_use_case.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/domain/entities/preparation_entity.dart'; import 'package:on_time_front/domain/repositories/preparation_repository.dart'; @@ -5,8 +6,9 @@ import 'package:on_time_front/domain/repositories/preparation_repository.dart'; @Injectable() class CreateCustomPreparationUseCase { final PreparationRepository _preparationRepository; + final ReconcileAlarmsUseCase _reconcile; - CreateCustomPreparationUseCase(this._preparationRepository); + CreateCustomPreparationUseCase(this._preparationRepository, this._reconcile); Future call( PreparationEntity preparationEntity, @@ -16,5 +18,6 @@ class CreateCustomPreparationUseCase { preparationEntity, scheduleId, ); + requestAlarmReconciliation(_reconcile); } } diff --git a/lib/domain/use-cases/reconcile_alarms_use_case.dart b/lib/domain/use-cases/reconcile_alarms_use_case.dart index 9a4d56ac..7ab8723e 100644 --- a/lib/domain/use-cases/reconcile_alarms_use_case.dart +++ b/lib/domain/use-cases/reconcile_alarms_use_case.dart @@ -1,8 +1,11 @@ +import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; import 'package:on_time_front/core/services/fallback_alarm_notification_service.dart'; import 'package:on_time_front/core/services/local_time_zone_service.dart'; import 'package:on_time_front/domain/entities/alarm_delivery_policy.dart'; @@ -27,16 +30,28 @@ class ReconcileAlarmsUseCase { final AlarmNowProvider _nowProvider; final String Function() _languageCodeProvider; final Future Function() _timeZoneProvider; - Future? _inFlight; + final AlarmOperationCoordinator _operations; + final _requests = <_ReconciliationRequest>[]; + int _revision = 0; + bool _draining = false; + + AlarmRegistrationCleanup get _cleanup => AlarmRegistrationCleanup( + _registryRepository, + _schedulerService, + _fallbackNotificationService, + _operations, + ); ReconcileAlarmsUseCase( this._alarmRepository, this._registryRepository, this._schedulerService, - this._fallbackNotificationService, - ) : _nowProvider = DateTime.now, - _languageCodeProvider = _currentLanguageCode, - _timeZoneProvider = LocalTimeZoneService.current; + this._fallbackNotificationService, { + @ignoreParam AlarmOperationCoordinator? operations, + }) : _operations = operations ?? AlarmOperationCoordinator.shared, + _nowProvider = DateTime.now, + _languageCodeProvider = _currentLanguageCode, + _timeZoneProvider = LocalTimeZoneService.current; @visibleForTesting ReconcileAlarmsUseCase.test( @@ -47,7 +62,9 @@ class ReconcileAlarmsUseCase { required AlarmNowProvider nowProvider, String Function()? languageCodeProvider, Future Function()? timeZoneProvider, - }) : _nowProvider = nowProvider, + AlarmOperationCoordinator? operations, + }) : _operations = operations ?? AlarmOperationCoordinator.shared, + _nowProvider = nowProvider, _languageCodeProvider = languageCodeProvider ?? _currentLanguageCode, _timeZoneProvider = timeZoneProvider ?? LocalTimeZoneService.current; @@ -55,28 +72,70 @@ class ReconcileAlarmsUseCase { ui.PlatformDispatcher.instance.locale.languageCode; Future call() { - final running = _inFlight; - if (running != null) { - AppLogger.debug('$_logTag call joined existing in-flight reconciliation'); - return running; + final AlarmOperationLease lease; + try { + lease = _operations.capture(); + } catch (error, stack) { + return Future.error(error, stack); + } + final request = _ReconciliationRequest(++_revision, lease); + _requests.add(request); + if (!_draining) { + _draining = true; + _operations.addListener(_invalidateRequests); + unawaited(_drain()); + } + return request.completer.future; + } + + void _invalidateRequests() { + for (final request in _requests) { + if (!request.lease.isCurrent && !request.completer.isCompleted) { + request.completer.completeError(const AlarmOperationInvalidated()); + } } + } - late final Future pending; - pending = _run().whenComplete(() { - if (identical(_inFlight, pending)) { - _inFlight = null; + Future _drain() async { + try { + while (_requests.isNotEmpty) { + final batch = List<_ReconciliationRequest>.of(_requests); + final cutoff = batch.last.revision; + try { + final result = await _operations.run( + batch.last.lease, + () => _run(batch.last.lease), + ); + batch.last.lease.check(); + for (final request in batch) { + if (!request.completer.isCompleted) { + request.completer.complete(result); + } + } + } catch (error, stack) { + for (final request in batch) { + if (!request.completer.isCompleted) { + request.completer.completeError(error, stack); + } + } + } + _requests.removeWhere((request) => request.revision <= cutoff); } - }); - _inFlight = pending; - return pending; + } finally { + // No await between the final queue check and idle transition. + _operations.removeListener(_invalidateRequests); + _draining = false; + } } - Future _run() async { + Future _run(AlarmOperationLease lease) async { + lease.check(); final now = _nowProvider(); final scheduleWindowStart = now; final scheduleWindowEnd = DateTime(now.year + 50, 1, 1); final alarmCoverageStart = now; final capabilities = await _schedulerService.getCapabilities(); + lease.check(); final alarmCoverageEnd = scheduleWindowEnd; AppLogger.debug( '$_logTag start now=${now.toIso8601String()} ' @@ -95,11 +154,13 @@ class ReconcileAlarmsUseCase { AlarmSettings settings; try { settings = await _alarmRepository.getAlarmSettings(); + lease.check(); AppLogger.debug( '$_logTag settings alarmsEnabled=${settings.alarmsEnabled} ' 'alarmOffset=${settings.alarmOffset}', ); } catch (_) { + lease.check(); AppLogger.debug('$_logTag settings unavailable'); final result = _result( status: AlarmReconciliationStatus.settingsUnavailable, @@ -113,7 +174,7 @@ class ReconcileAlarmsUseCase { } if (!settings.alarmsEnabled) { - final stored = await _registryRepository.loadAll(); + final stored = await _operations.loadRecords(_registryRepository); final observed = await _observeFallback(); final records = [...stored, ..._fallbackOrphans(observed, stored)]; final nativeObserved = await _observeNative(records, capabilities); @@ -121,8 +182,13 @@ class ReconcileAlarmsUseCase { AppLogger.debug( '$_logTag alarms disabled; canceling existingRecords=${records.length}', ); + lease.check(); final failedCancellations = await _cancelRecords(records); - await _registryRepository.replaceAll(failedCancellations); + lease.check(); + await _operations.replaceRecords( + _registryRepository, + failedCancellations, + ); final result = _result( status: failedCancellations.isEmpty && @@ -159,10 +225,12 @@ class ReconcileAlarmsUseCase { scheduleWindowStart, scheduleWindowEnd, ); + lease.check(); AppLogger.debug( '$_logTag getAlarmWindow success count=${schedules.length}', ); } catch (error) { + lease.check(); AppLogger.debug('$_logTag getAlarmWindow failed: $error'); final result = _result( status: AlarmReconciliationStatus.partial, @@ -210,7 +278,7 @@ class ReconcileAlarmsUseCase { 'desired=${_recordSummary(desiredRecords)}', ); - final storedRecords = await _registryRepository.loadAll(); + final storedRecords = await _operations.loadRecords(_registryRepository); final fallbackObservation = await _observeFallback(); final existingRecords = [ ...storedRecords, @@ -224,9 +292,6 @@ class ReconcileAlarmsUseCase { '$_logTag existingRecords=${existingRecords.length} ' 'existing=${_recordSummary(existingRecords)}', ); - final existingByScheduleId = { - for (final record in existingRecords) record.scheduleId: record, - }; final desiredByScheduleId = { for (final record in desiredRecords) record.scheduleId: record, }; @@ -256,9 +321,26 @@ class ReconcileAlarmsUseCase { fallbackPermission: fallbackPermission, ); + final existingByScheduleId = {}; + for (final record in existingRecords) { + final desired = desiredByScheduleId[record.scheduleId]; + final previous = existingByScheduleId[record.scheduleId]; + bool keepable(ScheduledAlarmRecord value) => + desired != null && + !value.cancellationPending && + _recordMatches(value, desired) && + value.provider == deliveryPolicy.activeProvider && + timingMatches(value); + if (previous == null || (!keepable(previous) && keepable(record))) { + existingByScheduleId[record.scheduleId] = record; + } + } + + lease.check(); final staleRecords = existingRecords.where((record) { final desired = desiredByScheduleId[record.scheduleId]; - return !deliveryPolicy.canDeliver || + return !identical(record, existingByScheduleId[record.scheduleId]) || + !deliveryPolicy.canDeliver || desired == null || !_recordMatches(record, desired) || _fallbackIdentityConflict(record, fallbackObservation) || @@ -271,12 +353,19 @@ class ReconcileAlarmsUseCase { 'stale=${_recordSummary(staleRecords)}', ); final failedCancellations = await _cancelRecords(staleRecords); + lease.check(); final blockedScheduleIds = failedCancellations .map((record) => record.scheduleId) .toSet(); final finalRecords = []; - final retainedRecords = []; + final retainedRecords = [ + ...existingRecords.where( + (record) => + blockedScheduleIds.contains(record.scheduleId) && + !staleRecords.contains(record), + ), + ]; final failures = [ ..._cancellationFailures(failedCancellations), if (!fallbackObservation.available && @@ -291,6 +380,7 @@ class ReconcileAlarmsUseCase { AlarmPermissionIssue? permissionIssue; for (final desired in desiredRecords) { + lease.check(); // Never overwrite cancellation evidence or report an old detailed // registration as a newly armed private one. if (blockedScheduleIds.contains(desired.scheduleId)) continue; @@ -333,6 +423,7 @@ class ReconcileAlarmsUseCase { : desired; final scheduled = await _scheduleRecord( reapplied, + lease: lease, capabilities: capabilities, deliveryPolicy: deliveryPolicy, fallbackPermission: fallbackPermission, @@ -396,7 +487,8 @@ class ReconcileAlarmsUseCase { failures.add(_observationFailure(scheduleId: record.scheduleId)); } } - await _registryRepository.replaceAll([ + lease.check(); + await _operations.replaceRecords(_registryRepository, [ ...confirmedRecords, ...retainedRecords, ...failedCancellations, @@ -504,6 +596,7 @@ class ReconcileAlarmsUseCase { Future<_ScheduleAttempt> _scheduleRecord( ScheduledAlarmRecord desired, { + required AlarmOperationLease lease, required AlarmSchedulerCapabilities capabilities, required AlarmDeliveryPolicy deliveryPolicy, required AlarmPermissionState fallbackPermission, @@ -512,7 +605,9 @@ class ReconcileAlarmsUseCase { final record = desired.copyWith( provider: capabilities.nativeAlarmProvider, ); + lease.check(); await _rememberOwnership([record]); + lease.check(); try { if (!record.alarmTime.isAfter(_nowProvider())) { return const _ScheduleAttempt( @@ -520,8 +615,10 @@ class ReconcileAlarmsUseCase { ); } await _schedulerService.scheduleNativeAlarm(record); + lease.check(); return _ScheduleAttempt(record: record); } catch (error) { + if (error is AlarmOperationInvalidated) rethrow; // A channel error can follow an OS write. Confirm cleanup before // changing providers; do not create two deliveries for one Schedule. final pending = await _cancelRecords([record]); @@ -531,6 +628,7 @@ class ReconcileAlarmsUseCase { failureReason: AlarmFailureReason.cancellationFailed, ); } + lease.check(); if (fallbackPermission != AlarmPermissionState.granted) { return _ScheduleAttempt( failureReason: error is AlarmSchedulingException @@ -552,7 +650,9 @@ class ReconcileAlarmsUseCase { final record = desired.copyWith( provider: AlarmProvider.localNotification, ); + lease.check(); await _rememberOwnership([record]); + lease.check(); try { if (!record.alarmTime.isAfter(_nowProvider())) { return const _ScheduleAttempt( @@ -562,11 +662,14 @@ class ReconcileAlarmsUseCase { final timing = await _fallbackNotificationService.scheduleFallbackAlarm( record, ); + lease.check(); return _ScheduleAttempt( record: record.copyWith(notificationTiming: timing), ); } catch (error) { + if (error is AlarmOperationInvalidated) rethrow; final pending = await _cancelRecords([record]); + lease.check(); return _ScheduleAttempt( pendingRecord: pending.firstOrNull, message: error is AlarmSchedulingException @@ -795,48 +898,12 @@ class ReconcileAlarmsUseCase { ); } - String _ownershipKey(ScheduledAlarmRecord record) => - '${record.provider.name}:${record.provider == AlarmProvider.localNotification ? record.fallbackNotificationId ?? stableAlarmId(record.scheduleId) : record.scheduleId}'; - - Future _rememberOwnership(List records) async { - final stored = await _registryRepository.loadAll(); - final keys = records.map(_ownershipKey).toSet(); - await _registryRepository.replaceAll([ - ...stored.where((record) => !keys.contains(_ownershipKey(record))), - ...records.map((record) => record.copyWith(cancellationPending: true)), - ]); - } + Future _rememberOwnership(List records) => + _operations.remember(_registryRepository, records); Future> _cancelRecords( List records, - ) async { - final failed = []; - if (records.isNotEmpty) await _rememberOwnership(records); - for (final record in records) { - try { - if (record.provider == AlarmProvider.localNotification) { - AppLogger.debug( - '$_logTag cancel fallback ' - 'scheduleId=${record.scheduleId} provider=${record.provider}', - ); - await _fallbackNotificationService.cancelFallbackAlarm(record); - } else if (record.provider != AlarmProvider.none) { - AppLogger.debug( - '$_logTag cancel native ' - 'scheduleId=${record.scheduleId} provider=${record.provider}', - ); - await _schedulerService.cancelNativeAlarm(record); - } - } catch (_) { - failed.add(record.copyWith(cancellationPending: true)); - AppLogger.debug( - '$_logTag cancel failed; retaining retry evidence ' - 'scheduleId=${record.scheduleId} provider=${record.provider}', - ); - } - } - return failed; - } + ) => _cleanup.cancelRecords(records); AlarmReconciliationResult _result({ required AlarmReconciliationStatus status, @@ -894,3 +961,25 @@ class _ScheduleAttempt { this.message, }); } + +final class _ReconciliationRequest { + _ReconciliationRequest(this.revision, this.lease); + final int revision; + final AlarmOperationLease lease; + final completer = Completer(); +} + +/// Acceptance is synchronous; errors remain observed where DB saves do not wait +/// for operating-system delivery effects. +void requestAlarmReconciliation(ReconcileAlarmsUseCase reconcile) { + unawaited( + reconcile().then( + (_) {}, + onError: (Object error, StackTrace _) { + AppLogger.debug( + '[ReconcileAlarms] request ended errorType=${error.runtimeType}', + ); + }, + ), + ); +} diff --git a/lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart b/lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart index 03cc6f61..642ef512 100644 --- a/lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart +++ b/lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart @@ -23,10 +23,19 @@ class ScheduleMutationAlarmEffectsCoordinator { required ScheduleMutationAlarmOperation operation, required String scheduleId, }) async { + // Acceptance is synchronous after the durable commit, even if targeted + // cancellation subsequently fails. The drain will retry from current data. + final reconciliation = _reconcileAlarmsUseCase(); + _observe(reconciliation, operation, scheduleId); if (_requiresTargetedCancellation(operation)) { - await _cancelScheduleAlarmUseCase(scheduleId); + try { + await _cancelScheduleAlarmUseCase(scheduleId); + } catch (error) { + AppLogger.debug( + '$_logTag cancellation incomplete errorType=${error.runtimeType}', + ); + } } - _reconcileBestEffort(operation, scheduleId); } bool _requiresTargetedCancellation(ScheduleMutationAlarmOperation operation) { @@ -34,22 +43,20 @@ class ScheduleMutationAlarmEffectsCoordinator { operation == ScheduleMutationAlarmOperation.finished; } - void _reconcileBestEffort( + void _observe( + Future reconciliation, ScheduleMutationAlarmOperation operation, String scheduleId, ) { unawaited( - _reconcileAlarmsUseCase().then((_) {}).catchError(( - Object error, - StackTrace _, - ) { - AppLogger.debug( - '$_logTag reconcile failed ' - 'operation=${operation.name} ' - 'scheduleId=$scheduleId ' - 'errorType=${error.runtimeType}', - ); - }), + reconciliation.then( + (_) {}, + onError: (Object error, StackTrace _) { + AppLogger.debug( + '$_logTag reconcile failed operation=${operation.name} errorType=${error.runtimeType}', + ); + }, + ), ); } } diff --git a/lib/domain/use-cases/schedule_preparation_session_use_case.dart b/lib/domain/use-cases/schedule_preparation_session_use_case.dart index c49033c4..5585cbe8 100644 --- a/lib/domain/use-cases/schedule_preparation_session_use_case.dart +++ b/lib/domain/use-cases/schedule_preparation_session_use_case.dart @@ -1,3 +1,5 @@ +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/logging/app_logger.dart'; import 'package:on_time_front/domain/entities/schedule_not_found.dart'; import 'dart:async'; import 'package:on_time_front/domain/entities/preparation_snapshot_validation.dart'; @@ -74,7 +76,8 @@ class SchedulePreparationSessionUseCase { startedAt: startedAt, ); await startSchedulePreparation(schedule.id); - await _cancelScheduleAlarmUseCase(schedule.id); + requestAlarmReconciliation(_reconcileAlarmsUseCase); + if (!await _cancelDelivery(schedule.id)) return; await saveTimedPreparationSnapshot( schedule, savedAt: startedAt, @@ -172,9 +175,9 @@ class SchedulePreparationSessionUseCase { }) async { await startSchedulePreparation(scheduleId); await _scheduleRepository.finishSchedule(scheduleId, latenessTime); - await _cancelScheduleAlarmUseCase(scheduleId); + requestAlarmReconciliation(_reconcileAlarmsUseCase); + if (!await _cancelDelivery(scheduleId)) return; await clearPersistedState(scheduleId); - unawaited(_reconcileAlarmsUseCase()); } Future resolvePromptedSchedule({ @@ -190,7 +193,9 @@ class SchedulePreparationSessionUseCase { return const SchedulePreparationPromptResult.unavailable(); } if (_isEnded(schedule.doneStatus)) { - if (isCurrent == null) await _cancelScheduleAlarmUseCase(scheduleId); + if (isCurrent == null && !await _cancelDelivery(scheduleId)) { + return const SchedulePreparationPromptResult.unavailable(); + } return const SchedulePreparationPromptResult.rejected(); } // Subscribe first, but retain only a finite snapshot of the loaded map. @@ -236,7 +241,9 @@ class SchedulePreparationSessionUseCase { if (scheduleFingerprint != null && scheduleFingerprint != combined.cacheFingerprint && !startPreparation) { - if (isCurrent == null) await _cancelScheduleAlarmUseCase(scheduleId); + if (isCurrent == null && !await _cancelDelivery(scheduleId)) { + return const SchedulePreparationPromptResult.unavailable(); + } return const SchedulePreparationPromptResult.rejected(); } return SchedulePreparationPromptResult.ready(combined); @@ -249,6 +256,21 @@ class SchedulePreparationSessionUseCase { } } + Future _cancelDelivery(String scheduleId) async { + try { + await _cancelScheduleAlarmUseCase(scheduleId); + return true; + } on AlarmOperationInvalidated { + // The old DB action must not write/clear transient state in a new store. + return false; + } catch (error) { + AppLogger.debug( + '[PreparationSession] delivery cleanup incomplete errorType=${error.runtimeType}', + ); + return true; // Durable start/finish succeeded; ownership remains for retry. + } + } + bool _isEnded(ScheduleDoneStatus doneStatus) { return doneStatus == ScheduleDoneStatus.normalEnd || doneStatus == ScheduleDoneStatus.lateEnd || diff --git a/lib/domain/use-cases/update_default_preparation_use_case.dart b/lib/domain/use-cases/update_default_preparation_use_case.dart index c201841b..fbb3b73a 100644 --- a/lib/domain/use-cases/update_default_preparation_use_case.dart +++ b/lib/domain/use-cases/update_default_preparation_use_case.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/domain/entities/preparation_entity.dart'; import 'package:on_time_front/domain/repositories/preparation_repository.dart'; @@ -5,10 +6,12 @@ import 'package:on_time_front/domain/repositories/preparation_repository.dart'; @Injectable() class UpdateDefaultPreparationUseCase { final PreparationRepository _preparationRepository; + final ReconcileAlarmsUseCase _reconcile; - UpdateDefaultPreparationUseCase(this._preparationRepository); + UpdateDefaultPreparationUseCase(this._preparationRepository, this._reconcile); Future call(PreparationEntity preparationEntity) async { await _preparationRepository.updateDefaultPreparation(preparationEntity); + requestAlarmReconciliation(_reconcile); } } diff --git a/lib/domain/use-cases/update_preparation_by_schedule_id_use_case.dart b/lib/domain/use-cases/update_preparation_by_schedule_id_use_case.dart index a1c1bf6a..493a99e2 100644 --- a/lib/domain/use-cases/update_preparation_by_schedule_id_use_case.dart +++ b/lib/domain/use-cases/update_preparation_by_schedule_id_use_case.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/domain/entities/preparation_entity.dart'; import 'package:on_time_front/domain/repositories/preparation_repository.dart'; @@ -5,8 +6,12 @@ import 'package:on_time_front/domain/repositories/preparation_repository.dart'; @Injectable() class UpdatePreparationByScheduleIdUseCase { final PreparationRepository _preparationRepository; + final ReconcileAlarmsUseCase _reconcile; - UpdatePreparationByScheduleIdUseCase(this._preparationRepository); + UpdatePreparationByScheduleIdUseCase( + this._preparationRepository, + this._reconcile, + ); Future call( PreparationEntity preparationEntity, @@ -16,5 +21,6 @@ class UpdatePreparationByScheduleIdUseCase { preparationEntity, scheduleId, ); + requestAlarmReconciliation(_reconcile); } } diff --git a/lib/presentation/app/bloc/auth/auth_bloc.dart b/lib/presentation/app/bloc/auth/auth_bloc.dart index 7c2e9d9e..a303b8b3 100644 --- a/lib/presentation/app/bloc/auth/auth_bloc.dart +++ b/lib/presentation/app/bloc/auth/auth_bloc.dart @@ -76,7 +76,7 @@ class AuthBloc extends Bloc { await Future.delayed(const Duration(milliseconds: 0)); if (state.status == AuthStatus.authenticated) { _scheduleBloc.add(const ScheduleSubscriptionRequested()); - unawaited(_reconcileAlarmsUseCase()); + requestAlarmReconciliation(_reconcileAlarmsUseCase); } }, onError: addError, diff --git a/lib/presentation/app/cubit/alarm_gate_cubit.dart b/lib/presentation/app/cubit/alarm_gate_cubit.dart index 41df55c8..8cad9380 100644 --- a/lib/presentation/app/cubit/alarm_gate_cubit.dart +++ b/lib/presentation/app/cubit/alarm_gate_cubit.dart @@ -206,6 +206,7 @@ class AlarmGateCubit extends Cubit { Future _disableAlarmsBestEffort() async { try { await _alarmRepository.updateAlarmSettings(alarmsEnabled: false); + requestAlarmReconciliation(_reconcileAlarmsUseCase); await _cancelAllAlarmsUseCase(); } catch (error) { AppLogger.debug( diff --git a/lib/presentation/app/screens/app.dart b/lib/presentation/app/screens/app.dart index 87169207..71f42433 100644 --- a/lib/presentation/app/screens/app.dart +++ b/lib/presentation/app/screens/app.dart @@ -170,7 +170,7 @@ class _AppRouterViewState extends State<_AppRouterView> disableAlarmsWhenPermissionMissing: true, ), ); - unawaited(getIt.get()()); + requestAlarmReconciliation(getIt.get()); } void _handleAlarmLaunchPayload(Map payload) { diff --git a/lib/presentation/my_page/my_page_screen.dart b/lib/presentation/my_page/my_page_screen.dart index 7954a031..9f68e787 100644 --- a/lib/presentation/my_page/my_page_screen.dart +++ b/lib/presentation/my_page/my_page_screen.dart @@ -136,7 +136,24 @@ class _DetailedNotificationTileState extends State<_DetailedNotificationTile> { Future _change(bool enabled) async { setState(() => _enabled = enabled); await getIt().setEnabled(enabled); - await getIt()(); + var incomplete = false; + try { + final result = await getIt()(); + incomplete = + result.status == AlarmReconciliationStatus.partial || + result.status == AlarmReconciliationStatus.settingsUnavailable; + } catch (_) { + incomplete = true; + } + if (incomplete && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.notificationIncompleteStatus, + ), + ), + ); + } } @override @@ -305,6 +322,7 @@ class _AlarmStatusViewState extends State<_AlarmStatusView> if (!delivery.policy.canDeliver && delivery.policy.shouldDisableAlarms) { await alarmRepository.updateAlarmSettings(alarmsEnabled: false); + requestAlarmReconciliation(getIt()); await getIt.get()(); await _load(); return; @@ -325,6 +343,7 @@ class _AlarmStatusViewState extends State<_AlarmStatusView> if (!delivery.policy.canDeliver && delivery.policy.shouldDisableAlarms) { await alarmRepository.updateAlarmSettings(alarmsEnabled: false); + requestAlarmReconciliation(getIt()); await getIt.get()(); await _load(); return; @@ -332,6 +351,7 @@ class _AlarmStatusViewState extends State<_AlarmStatusView> await alarmRepository.updateAlarmSettings(alarmsEnabled: true); } else { await alarmRepository.updateAlarmSettings(alarmsEnabled: false); + requestAlarmReconciliation(getIt()); await getIt.get()(); } await _load(); @@ -344,6 +364,10 @@ class _AlarmStatusViewState extends State<_AlarmStatusView> mounted) { await _load(); } + } catch (_) { + // The preference may already be committed. Re-read it and expose the + // incomplete delivery state instead of undoing it or leaking a Future. + if (mounted) await _load(); } finally { if (mounted) { setState(() { diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 7dbe5c6e..8292a26e 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -24,8 +24,8 @@ | A11 | [#589](https://github.com/DevKor-github/OnTime-front/issues/589) | 089afee5 원격636 tests/84.96%·APK 통과, 최신main 통합678 tests 통과, 기기 검증 대기 | | A12 | [#591](https://github.com/DevKor-github/OnTime-front/issues/591) | 콜드 탭·선택 준비 유지 구현, 최종 b9f959db 원격707 tests/analyze·coverage87.11%·APK 통과, 기기 검증 대기 | | A13 | [#592](https://github.com/DevKor-github/OnTime-front/issues/592) | 예약/안내·registry 후속 수정 원격740 tests/87.20%·APK 통과, 기기 대기 | -| A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측 구현282db21f, 원격772 tests/87.44%·APK 통과, A15 실제 ownership 저장 후속·기기 대기 | -| A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 실제4문답·요청별 완료/실패 및 세대 직렬화 기준 확정, 전담 구현 중 | +| A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측 구현282db21f, 원격772 tests/87.44%·APK 통과, A15 실제 ownership 후속 로컬795 통과·기기 대기 | +| A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 요청별 후속 재조정/교체 직렬화 구현, 최종 로컬795 tests/analyze 통과·원격/기기 대기 | | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 실제4문답·취소 journal/중단 복구/부분 결과 기준 확정, A15 뒤 구현 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | 실제3문답·정정 포함, 신선한 실제 타이머 전환 알림 기준 확정 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | 실제3문답·추가 정정 포함, DB 원자적 시작·run 시각·실패 재시도 기준 확정 | diff --git a/plans/audit-2026-09-23/a15-validation.json b/plans/audit-2026-09-23/a15-validation.json new file mode 100644 index 00000000..08042d70 --- /dev/null +++ b/plans/audit-2026-09-23/a15-validation.json @@ -0,0 +1,238 @@ +{ + "audit_id": "A15", + "issue_number": 594, + "recorded_at": "2026-09-24T01:47:52.715324+09:00", + "base_head": "d2ae69d3f0e139d2d88c46a7f1e0b8da5e403830", + "implementation_commit": null, + "scope": "Dart source, real Drift/SharedPreferences persistence, actual use-case/service integrations, blocking fake OS boundaries; no actual OS delivery proof", + "source_freeze": { + "file_count": 37, + "production_count": 21, + "test_count": 16, + "before_after_equal": true, + "before_aggregate_sha256": "de887b4e5d34a0ac9c37d62e92a652128fabde3ae1ddcb6dc54d6eccff446dd2", + "after_aggregate_sha256": "de887b4e5d34a0ac9c37d62e92a652128fabde3ae1ddcb6dc54d6eccff446dd2", + "algorithm": "SHA-256 of UTF-8 compact JSON of path-to-SHA256 map with sorted keys", + "before_raw_path": "/tmp/a15-source-final-before.json", + "after_raw_path": "/tmp/a15-source-final-after.json", + "source_sha256": { + "lib/core/backup/backup_service.dart": "9e9115611b34037085c81c458b2c448ff5db5f9aa6824127a3f895db8bfee651", + "lib/core/database/local_data_operation_gate.dart": "ca8bfc6f0524d72bd31fb6edd7b460c9fbb920b82a8115eee765903a855df431", + "lib/core/database/local_data_reset_service.dart": "5a68174b12e492561e6ffd01339910d9baefb24c59f6e0a2e15bc6600be230fe", + "lib/core/services/alarm_operation_coordinator.dart": "3f0c722ef010266df9b33170fe4296f9ce38a10bcc41c1dfffefc5ad712bc9e6", + "lib/core/services/alarm_registration_cleanup.dart": "ec761f598843f1bb4e921b94220f2148a5b09b2adc1d6f663c163afe700dfe75", + "lib/core/services/runtime_privacy_migration.dart": "2af68dea5f0ad56fcc356e7c7dfcb60f476de4c749c60b2dc1afdff11b171100", + "lib/data/data_sources/alarm_registry_local_data_source.dart": "c6630f012bc4b7a30182aa5981171dd87c77672ba09980897402e9a5470ec091", + "lib/data/repositories/alarm_registry_repository_impl.dart": "5dbc52c6e3374d5e4832c31d6f588182ae03a2f686860cecea79e3444d81d772", + "lib/domain/entities/alarm_entities.dart": "8b293fe26b9e427767f0e27e1c241f3fa67f355c3be63c7db03319f49ba2f140", + "lib/domain/use-cases/cancel_all_alarms_use_case.dart": "5132abe8512f86cd3d9854331f132c63196b33c5966c7d444195014f12b843eb", + "lib/domain/use-cases/cancel_schedule_alarm_use_case.dart": "910edcb21f162d0720c4bd3ee78b40ef05abb571b02eeae7ae4a0e4536bc4939", + "lib/domain/use-cases/create_custom_preparation_use_case.dart": "9e65bad6207798a9b5388e49d9d41e47e58334a7ff6a2cbe629c8f7976c179e6", + "lib/domain/use-cases/reconcile_alarms_use_case.dart": "3be8e6c940f4657ec31fbdfe0de7291e58b1f45294c05878f141d604736c0a14", + "lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart": "a74af417500c2808c6dcf47acbe1e4a6074428d5076baa94cc6c2071bc37c6e1", + "lib/domain/use-cases/schedule_preparation_session_use_case.dart": "94a0ac310c64127682cf26cb1d46568a767d6a106e68c8726281038216938a22", + "lib/domain/use-cases/update_default_preparation_use_case.dart": "f82e82a391bdb3bd956ecedba14e2b0d00e01848b1b5aac5d3828c50171fab41", + "lib/domain/use-cases/update_preparation_by_schedule_id_use_case.dart": "364f367b7b167b03d39b181724a3116e48e369910750e3f2aa7c54ce1e9e4496", + "lib/presentation/app/bloc/auth/auth_bloc.dart": "b8b6787ca4e4cc4ad4b0dd0589ccaab80625361813a825c82d1826aac79a3eed", + "lib/presentation/app/cubit/alarm_gate_cubit.dart": "2e97460db5f79359ae97dd6fd85e1fa2dfccc2e493e72c66a3766898444cdbbd", + "lib/presentation/app/screens/app.dart": "a70e26a65959f877d747240ec6dfc56508ea787fdab5d786eab4f017dab834ac", + "lib/presentation/my_page/my_page_screen.dart": "4ca549582472ae6ce460fd27681ba1505e5a8d042c03a89ed61b600027f0bf37", + "test/core/backup/backup_restore_picker_test.dart": "e1e4fd9bf75d61be96a9bbb0dbc52ad449f7a310c5e48d6386baa669086261f0", + "test/core/backup/backup_service_test.dart": "a414c730497e3459e615f0c56b1db2232c463055b6a52df80e9cd2fb4ecc7547", + "test/core/database/alarm_replacement_integration_test.dart": "1f5b4753fcd41b94166f7bb44f0869bcb82b9cf314d154eeb14b4a718895c247", + "test/data/repositories/alarm_registry_repository_impl_test.dart": "6413a15f52de0ecc764888e33a0b097933233f7760a4138b570f91a61d5ba3ec", + "test/domain/use-cases/alarm_reconciliation_concurrency_test.dart": "065806b405504892aaf5c2f00560cdd8de22955e7ae13d532251c95753aa53bb", + "test/domain/use-cases/cancel_alarms_use_cases_test.dart": "b7da59e69109ce0faa5cc497c7e4a65cb6bfb2eecc8a51b24f21de690997c5ba", + "test/domain/use-cases/reconcile_alarms_use_case_test.dart": "a9372393dca8435a937b5fc54e714bcfbfb5a6d060d979e11e0a0a7437f9f9c0", + "test/domain/use-cases/recurring_alarm_integration_test.dart": "ce2f5ee98073ceabd55a21b6bd5a4695da81ce7a6dac50d34e9fc5ca12858da3", + "test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart": "b767e76b1bd30c61b370de46f1f4b791224317ca270c2c1fd1580a0ca4af8253", + "test/domain/use-cases/schedule_preparation_session_use_case_test.dart": "a01103a4a356a0b72356c95d2a469fcd50ad724be17ff901265b77c83af88a08", + "test/domain/use-cases/timed_preparation_use_cases_test.dart": "36f30958c8ccb72d7d999b3fab9ce53b04f5dab3628f837882147cc41aa0719e", + "test/helpers/noop_alarm_cleanup.dart": "25ff39efd57c1bd6fe5f3cedde19abd1b88fb114d4e703b96cb429751b70a59c", + "test/helpers/noop_alarm_reconciliation.dart": "dd2a9e0f9888a7c64466daca2b16e78bac3a6a2361a997d69ca7ad6355548fd9", + "test/presentation/my_page/my_data_restore_test.dart": "015004e6c96f8c6608768cab8532bf1e953c7507cec214149bfc810409fd9895", + "test/presentation/my_page/my_page_screen_test.dart": "4f6c9577f8ba8572e28be470764fa9f47e4345d09f56e13b40e5bbbafbdb058f", + "test/presentation/recurring/recurring_form_integration_test.dart": "889e5c4cd7f1d954f3eb2eb2d18c7b226f93a8ac6e40ae00962393a90b4506cd" + }, + "base_head_source_sha256": { + "lib/core/backup/backup_service.dart": "3ce7559eba72034e18db31afabc3418bfb6e2f7aa13d0cbc2ffdd2d04286f29f", + "lib/core/database/local_data_operation_gate.dart": "eddfd35db2879f169fc438b4ca2e41793547437f3a9bfae5a64dcc2ae9e97299", + "lib/core/database/local_data_reset_service.dart": "470b5ddeeb48e55fba618af70b2e1c17750db1197ccb5e00e262155cfe4f8a2f", + "lib/core/services/alarm_operation_coordinator.dart": null, + "lib/core/services/alarm_registration_cleanup.dart": null, + "lib/core/services/runtime_privacy_migration.dart": "055c3c42ce1c8db14c5e0c0be32b9d8acf60cf939d582728e43c888ffbd8fcea", + "lib/data/data_sources/alarm_registry_local_data_source.dart": "1369c3435ad9e83480481d07533a99e76972da866cde28279324a951f772b098", + "lib/data/repositories/alarm_registry_repository_impl.dart": "064eb8e4626e1a35b9fdb14089d8a3db7d53df6c8daaa6a220537ddeb5416f6f", + "lib/domain/entities/alarm_entities.dart": "e553b399b33366e66d97f4d9a48be51f893b2665d3526de6863996dc3df9f5eb", + "lib/domain/use-cases/cancel_all_alarms_use_case.dart": "a0552a206ca23434b377b5d12838afb9c6868dc611bc5a1c019f691fe991f2b5", + "lib/domain/use-cases/cancel_schedule_alarm_use_case.dart": "c6545be67ddd695f458f740120ff261a37f50a110b3ba137b60cbf49a7683588", + "lib/domain/use-cases/create_custom_preparation_use_case.dart": "ad0594dac6b8fd8036c591cb90c3cb6773ed47ffcf7ef1e6825c70ccd5bc0e10", + "lib/domain/use-cases/reconcile_alarms_use_case.dart": "b35da04adb14635ff3e49d7c20bcd990fb999188cfaf4901216ee75e6a2cab55", + "lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart": "aea586206a5176bcd5ea47879e3336affea9fed8fd17febb362265a7479c63c7", + "lib/domain/use-cases/schedule_preparation_session_use_case.dart": "e50d09d05abbb455efe5988835c78bd11aaaca00ced486aa0618735660ebb8b8", + "lib/domain/use-cases/update_default_preparation_use_case.dart": "d9908f62cd0664fb5b6105cf242489ac5c2c7184536efe4228785ce9ee9eae71", + "lib/domain/use-cases/update_preparation_by_schedule_id_use_case.dart": "8e6bddc5f8a7a0b0611ba54a8936415e10f148a354abc9143bc30122456b5dc4", + "lib/presentation/app/bloc/auth/auth_bloc.dart": "3ef6696aa67c820bfe1e156200affbbaca7295b54dca16853a2d91b81f878d85", + "lib/presentation/app/cubit/alarm_gate_cubit.dart": "3fc38676137cf6f43f83d0bd03d8d639d770171630dba9d0f5e75769e9bb21e7", + "lib/presentation/app/screens/app.dart": "1e2bf2d22e0afcfa67739c951eabbe701d0bf1f3f21d1121d74ca1620dd2c788", + "lib/presentation/my_page/my_page_screen.dart": "a6b09b5008a5d1c550b98ca5b741e07eeaf5f49c295f63a6c6cd64ebdbfa4af9", + "test/core/backup/backup_restore_picker_test.dart": "0b009b213a12668cc5c97a72e8d20d4771b228bff73500eb215551ca033e6d85", + "test/core/backup/backup_service_test.dart": "1560a1c41881deea8ba8328afef67bccae0a2ff87589f4ac241ee2732c787e96", + "test/core/database/alarm_replacement_integration_test.dart": null, + "test/data/repositories/alarm_registry_repository_impl_test.dart": "3b13f9f905a1e19afb568539f22d21c198ffcb340dd85cf422b8c68d544b8640", + "test/domain/use-cases/alarm_reconciliation_concurrency_test.dart": null, + "test/domain/use-cases/cancel_alarms_use_cases_test.dart": "1f6a8d517962d5b768ee9c0f0a6c681b72f2d148d1bac4760b9dadf1950f9b31", + "test/domain/use-cases/reconcile_alarms_use_case_test.dart": "a3fcfc825fbb660cf51835847cceaf1d5223d1b62d8d3834f4df436beadf5b63", + "test/domain/use-cases/recurring_alarm_integration_test.dart": "4213d878156b6f446ecede4c9816cc3b51ba0729bfd1706e32722ef5b76ae49a", + "test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart": "f3dc6df80bcf768f123d375fd3c1780e42f2c3b83211aa1e0cc04649cd661811", + "test/domain/use-cases/schedule_preparation_session_use_case_test.dart": "f0f9f56df3a2eab969d105cb58695fae9a192a1a38d33c2fc82b487732c0d0c4", + "test/domain/use-cases/timed_preparation_use_cases_test.dart": "5eb010ee6b41b7f5de15a829d889d4baa31b742791b9c505185ebaf70bea1825", + "test/helpers/noop_alarm_cleanup.dart": null, + "test/helpers/noop_alarm_reconciliation.dart": null, + "test/presentation/my_page/my_data_restore_test.dart": "137c479fb85da20cc835ace34bd680a8ccdf44545e6edd7efce45956f80cb1bf", + "test/presentation/my_page/my_page_screen_test.dart": "d6f00ea706ec4b93fdde2ed37fd883c6fe193d866fd77544f61aa29bf858021a", + "test/presentation/recurring/recurring_form_integration_test.dart": "604eaa2702dcc54e213a55679c7d2596a3aa6038fe35bce303368644bfe44fc7" + } + }, + "commands": [ + { + "command": "dart run build_runner build --delete-conflicting-outputs", + "exit_code": 0, + "result": "314 ignored local outputs; 10 seconds; option deprecated/ignored by installed generator; SDK/analyzer version warning recorded", + "log": "generation" + }, + { + "command": "flutter test test/domain/use-cases/recurring_alarm_integration_test.dart --reporter expanded", + "exit_code": 0, + "passed": 5, + "evidence": "Strengthened actual create/update/preparation entrypoint tests do not inject a post-mutation reconcile request", + "log": "automatic_mutation" + }, + { + "command": "flutter test --reporter expanded", + "exit_code": 0, + "passed": 795, + "phase": "final frozen source after actual mutation test strengthening", + "log": "full" + }, + { + "command": "flutter analyze", + "exit_code": 0, + "result": "No issues found; 2.9 seconds", + "log": "analyzer" + }, + { + "command": "dart run tool/check_generated_dart_policy.dart", + "exit_code": 0, + "result": "outputs ignored and untracked", + "log": "generated_policy" + }, + { + "command": "dart run tool/check_local_only_boundary.dart", + "exit_code": 0, + "result": "Local-only product boundary verified", + "log": "local_boundary" + }, + { + "command": "git diff --check", + "exit_code": 0 + } + ], + "failed_runs": [ + { + "log": "red", + "result": "snapshot-after-mutation regression fails against previous coalescing behavior: expected committed-after-snapshot, actual empty list", + "resolution": "Revision/cutoff trailing drain implemented" + }, + { + "log": "initial_full_failure", + "result": "791 passed, 1 failed: old MyPage exact reconciliation call count expected2, actual3", + "resolution": "Removed implementation-count assertion; preserved committed preference/cancel/display assertions" + }, + { + "log": "ownership_failure", + "result": "62 passed, 1 failed: minimal cancellation record lost notificationTiming", + "resolution": "Preserve timing enum in ownershipOnly and actual storage sanitizer; added real SharedPreferences roundtrip assertion" + } + ], + "additional_review": { + "automatic_mutation_test_masking": "Final test review removed test-injected reconciliation after actual mutation; waits only on shared owner queue barrier. Same fix for deletion coordinator regression. Only two test files changed after earlier freeze; product source unchanged. Final full795/analyze rerun with independently captured before/after hash maps.", + "initial_freeze_aggregate": "92383a48e7e0e9ca5ab186a39ba25db66473af291a9117749ca31408cbdffa2e", + "actual_lifecycle": "3 tests instantiate actual BackupService/LocalDataResetService and real Drift; mocks limited to OS/key/prefs adapters. NoopAlarmCleanup elsewhere is isolation only.", + "actual_registry": "Actual SharedPreferences data source plus repository roundtrip/keeper tests cover multiple actual IDs and failures; old A14 fake registry suite did not establish this property." + }, + "logs": { + "red": { + "raw_path": "/tmp/a15-red.log", + "raw_sha256": "a9fe4facb3e9843c5d1ccd9639620aa168ea96375615df0c39693b55b80562bc", + "repository_copy": "plans/audit-2026-09-23/artifacts/a15/red.txt", + "repository_copy_sha256": "429af3a6ce7221abf258a8c7a8e395ae2e12d62c4de5243d7fffe0005e3890e6", + "normalization": "LF line endings and trailing whitespace removal; original retained" + }, + "generation": { + "raw_path": "/tmp/a15-codegen-final.log", + "raw_sha256": "8719835fd34396b9bea2e1cea65be2b9a301b45c73f7754fdb8c2a5fb120e1c8", + "repository_copy": "plans/audit-2026-09-23/artifacts/a15/generation.txt", + "repository_copy_sha256": "8719835fd34396b9bea2e1cea65be2b9a301b45c73f7754fdb8c2a5fb120e1c8", + "normalization": "LF line endings and trailing whitespace removal; original retained" + }, + "initial_full_failure": { + "raw_path": "/tmp/a15-full.log", + "raw_sha256": "694369cc91b8c2dc17353954ac7fa7f9b363515a4a45667b976a635fa76d83a1", + "repository_copy": "plans/audit-2026-09-23/artifacts/a15/initial_full_failure.txt", + "repository_copy_sha256": "a2b4f427be75a92182532e461e1d17a80abdd78df747ef3b8aa5a750361ce70e", + "normalization": "LF line endings and trailing whitespace removal; original retained" + }, + "ownership_failure": { + "raw_path": "/tmp/a15-ownership.log", + "raw_sha256": "3ca5e6b162e34843d5fc018111626610be6e689cdb63e0364fd0c5d08db82d26", + "repository_copy": "plans/audit-2026-09-23/artifacts/a15/ownership_failure.txt", + "repository_copy_sha256": "67394ed5f805512710a227195e3dce536086c3c41df9da8a2f473d6bbe5890a0", + "normalization": "LF line endings and trailing whitespace removal; original retained" + }, + "automatic_mutation": { + "raw_path": "/tmp/a15-mutation-automatic.log", + "raw_sha256": "786bbae9857ef025d00bdd9df6ca256bc5b9e9f62ff0234ab4a90ba3fbfad17a", + "repository_copy": "plans/audit-2026-09-23/artifacts/a15/automatic_mutation.txt", + "repository_copy_sha256": "786bbae9857ef025d00bdd9df6ca256bc5b9e9f62ff0234ab4a90ba3fbfad17a", + "normalization": "LF line endings and trailing whitespace removal; original retained" + }, + "full": { + "raw_path": "/tmp/a15-source-final-full.log", + "raw_sha256": "f21216416f39996029298ce3a5774df46baee6d60bd33e82fb734285a4ad671e", + "repository_copy": "plans/audit-2026-09-23/artifacts/a15/full.txt", + "repository_copy_sha256": "f21216416f39996029298ce3a5774df46baee6d60bd33e82fb734285a4ad671e", + "normalization": "LF line endings and trailing whitespace removal; original retained" + }, + "analyzer": { + "raw_path": "/tmp/a15-source-final-analyze.log", + "raw_sha256": "61dfef2a9720f3cfd638bf1767ebaf11fc0fb97cb07c122dd974665f0aa19c60", + "repository_copy": "plans/audit-2026-09-23/artifacts/a15/analyzer.txt", + "repository_copy_sha256": "02961fdf15a57099ad9e22f439cb5b8251d4e8e2730430674693e3d5623076da", + "normalization": "LF line endings and trailing whitespace removal; original retained" + }, + "generated_policy": { + "raw_path": "/tmp/a15-generated-policy.log", + "raw_sha256": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "repository_copy": "plans/audit-2026-09-23/artifacts/a15/generated_policy.txt", + "repository_copy_sha256": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "normalization": "LF line endings and trailing whitespace removal; original retained" + }, + "local_boundary": { + "raw_path": "/tmp/a15-local-boundary.log", + "raw_sha256": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "repository_copy": "plans/audit-2026-09-23/artifacts/a15/local_boundary.txt", + "repository_copy_sha256": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "normalization": "LF line endings and trailing whitespace removal; original retained" + } + }, + "limitations": [ + "Android/iOS real device permission, Doze, reboot, lock screen, timing and duplicates NOT RUN/device pending", + "Native source unchanged; native SDK build/APK/Runner linking not rerun by A15", + "Permanently pending native Future retains owner and can keep replacement pending; caller timeout does not cancel native side effect", + "In-memory ownership is not crash durability; D03 persistent journal and full interrupted-reset bootstrap recovery remain follow-up", + "Reset marker followed by cancellation failure leaves gate invalidated; same-process reset retry rejected; restart policy remains", + "D02 corrupted-DB recovery manifest not implemented", + "Multi-read transaction snapshot consistency remains A08/C01; broader settings save races remain U03" + ] +} diff --git a/plans/audit-2026-09-23/artifacts/a15/analyzer.txt b/plans/audit-2026-09-23/artifacts/a15/analyzer.txt new file mode 100644 index 00000000..dd8cdbfd --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a15/analyzer.txt @@ -0,0 +1,2 @@ +Analyzing OnTime-front... +No issues found! (ran in 2.9s) diff --git a/plans/audit-2026-09-23/artifacts/a15/automatic_mutation.txt b/plans/audit-2026-09-23/artifacts/a15/automatic_mutation.txt new file mode 100644 index 00000000..6357fb0c --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a15/automatic_mutation.txt @@ -0,0 +1,7 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart +00:00 +0: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:00 +1: actual default and custom preparation commits request latest delivery timing +00:00 +2: two unbounded series share the nearest 60 alarms and survive reopening +00:00 +3: deletion cancels one alarm, refills capacity and stays excluded +00:00 +4: a previous civil date in a negative offset still supplies a future alarm +00:00 +5: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a15/full.txt b/plans/audit-2026-09-23/artifacts/a15/full.txt new file mode 100644 index 00000000..c0b192a2 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a15/full.txt @@ -0,0 +1,800 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:01 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: normal civil time resolves to one absolute occurrence +00:01 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: normal civil time resolves to one absolute occurrence +00:01 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: spring-forward gap has no valid occurrence +00:01 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: fall-back overlap exposes first and second occurrence +00:01 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:01 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:02 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:02 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:02 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:03 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:03 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:03 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:03 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:03 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:03 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:04 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:04 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:04 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:05 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: localizedNotificationText selects Korean text only for Korean locale +00:05 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: isScheduleAlarmPayload detects native alarm payloads without suppressing schedule notifications +00:05 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule notification payload to the schedule start screen +00:05 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes five-minute prompts as early-start schedule starts +00:05 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule and preparation updates to the alarm screen +00:05 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString ignores null, invalid, and unrelated payloads +00:05 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: invalid schedule hints never route to nearest preparation +00:05 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForData routes decoded local payload data with the same rules +00:05 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:05 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:05 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:05 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:05 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: explicit exact denial retries once and returns actual approximate receipt +00:05 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unrelated provider error is never converted to approximate success +00:05 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: failed approximate retry stays failed and is not retried again +00:05 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unknown timing permission uses approximate rather than claiming exact +00:05 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: display denial blocks scheduling even with exact access +00:05 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: settings response is not proof of a grant: permission is reread +00:05 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: iOS timing access is unsupported without calling Android plugin +00:05 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real storage retains every failed platform ownership for the same Schedule +00:05 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:05 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:05 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:05 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:05 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:05 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:05 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:05 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:06 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:06 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:06 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:06 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:06 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk changes preserve only overridden fields and preparation +00:06 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: ending following leaves active and historical rows and their preparations intact +00:06 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:06 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:06 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: an override cannot be moved into another occurrence preparation window +00:06 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:06 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: same preferences and empty score reset are exact no-ops +00:06 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:06 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:06 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedule date ranges as start-inclusive and end-exclusive +00:06 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: concurrent finishes count distinct schedules once and duplicate only once +00:06 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:06 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:06 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:06 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:06 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: multiple preparation steps are a no-op even when caller omits links +00:06 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_data_source_contract_test.dart: schedule local data source contract does not expose domain entities +00:06 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: missing profile rejects updates and never reports a successful mutation +00:06 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: empty profile starts without a durable revision and creation is race safe +00:06 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: raw revision update notifies DB watchers and failed profile write publishes no success +00:07 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:07 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:07 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:07 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:07 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:07 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:07 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:07 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:07 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:07 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:07 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers map schedules to and from database rows preserving stored fields +00:07 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps users to and from database rows preserving profile values +00:07 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers empty users cannot be converted to database rows +00:07 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps places and preparation steps to database rows +00:07 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: saves and loads the early start timestamp per schedule +00:07 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:07 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:07 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:07 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:07 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:07 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:07 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return an empty preparation list when user has no steps +00:07 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:07 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps when rows are out of order +00:07 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should keep remaining preparation steps when a link is broken +00:07 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return each preparation step once when links form a cycle +00:07 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should return one preparation step by id +00:07 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:07 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:07 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: updatePreparationUser should update a preparation step +00:07 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:08 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:08 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:08 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: deleteSchedule should delete a schedule from the database +00:08 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getScheduleById should return a schedule from the database of given [id] +00:08 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:08 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] +00:08 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null +00:08 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:08 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:08 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:08 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: updatePreparationUser should update a preparation step +00:08 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations createPreparationSchedule stores steps as an ordered linked list +00:08 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns empty for no steps +00:08 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:08 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:08 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:08 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:08 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:08 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:08 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:08 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:08 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:08 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:08 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:08 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:08 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: weekly matching starts inclusively without adding the anchor day +00:08 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:08 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:08 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: spring gap is skipped and does not consume the count +00:08 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:08 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:08 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:08 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:08 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:08 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:08 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:08 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:08 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:08 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:08 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:08 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:08 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: deleted and finished schedules accept reconciliation before awaiting cancellation +00:08 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:08 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: reconciliation failures remain best-effort +00:08 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:08 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:08 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:08 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:08 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:08 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:08 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:08 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:08 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:08 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:08 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:08 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:08 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:08 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:08 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:08 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:08 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:08 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:08 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:08 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:08 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:08 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:08 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:08 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:08 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:08 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:08 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:08 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:08 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:08 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:08 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:08 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:08 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:08 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:08 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:09 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:09 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:09 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:09 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:09 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:09 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:09 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:09 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:09 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:09 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:09 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:09 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:09 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_date_use_cases_test.dart: GetSchedulesByDateUseCase watches requested schedule date range +00:10 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:10 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:10 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:10 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:10 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:10 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:10 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:10 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:10 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:10 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:10 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:10 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:10 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:10 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart: unchanged preparation updates the schedule without saving preparation +00:10 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:10 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:10 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:10 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: a previous civil date in a negative offset still supplies a future alarm +00:10 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: each cutoff finishes without waiting for later edits to become quiet +00:11 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failed snapshot completes its cutoff and still serves a newer request +00:11 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: registry write failure is not armed and a queued newer request recovers ownership +00:11 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: registry write failure is not armed and a queued newer request recovers ownership +00:11 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:11 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:11 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: request after schedule snapshot receives its own latest pass +00:11 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: a registry-owned ID with a conflicting route is cancelled before repair +00:11 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=true +00:11 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=true +00:11 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=false +00:11 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=false +00:11 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native revoke and grant switch providers after cancelling the old registration +00:11 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:11 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:11 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:11 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:11 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:11 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:11 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:11 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: confirmed fallback orphans are targeted; unrelated and unknown ownership survive +00:11 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:11 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native failure uses only the actual fallback receipt after native cleanup +00:11 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed native write cleanup forbids fallback and keeps cancellation identity +00:11 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:11 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: empty user exposes null convenience values +00:11 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:11 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:11 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:11 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:11 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:11 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:11 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:11 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities calculates left time before leave with explicit now +00:11 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports late status after leave time has passed +00:11 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities fast-forwards preparation steps for late entry +00:11 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities single preparation step tracks elapsed time and diagnostics +00:11 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities marks all steps done for very late entry inside schedule window +00:11 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports display helpers for current and completed steps +00:11 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities skipCurrentStep marks only the active step done +00:11 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities completed and empty preparations expose safe display fallbacks +00:11 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities cache fingerprint changes when preparation step name changes +00:11 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities schedule with preparation derives start time and total duration +00:11 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities combines a schedule entity with timed preparation preserving fields +00:11 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleEntity diagnostics string representation includes schedule identity for diagnostics +00:11 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleState timing helper durationUntilPreparationStartAt uses injected now deterministically +00:11 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:11 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:11 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: minimal progress hydrates names/durations from current source only +00:11 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy exact fingerprint additionally requires all step fields +00:11 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy launch becomes a minimal versioned route, never a start command +00:12 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:12 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:12 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:12 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:12 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:13 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (setUpAll) +00:13 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:14 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:14 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:14 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:14 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:14 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:14 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:14 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: delete action stays available for started unfinished schedule +00:15 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:15 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:15 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:15 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:15 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:15 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:15 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:15 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:15 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:15 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:16 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow emits notExists when there is no upcoming schedule +00:16 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:17 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:17 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:17 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:17 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:17 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:17 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:18 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:18 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:18 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +549: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +550: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +551: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +552: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +553: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +554: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +555: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +556: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:18 +557: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow same schedule re-emission preserves started state when early-started +00:18 +558: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish clears timed preparation and early session state +00:18 +559: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow confirmation-required run stays stopped after prompt until explicit start +00:18 +560: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow mismatched fingerprint invalidates cached progress +00:18 +561: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start when early session and snapshot exist +00:18 +562: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start with only early session derives elapsed from session start +00:18 +563: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt loads schedule details and arms the prompt +00:18 +564: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects ended schedules and clears their alarm +00:18 +565: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects stale fingerprint unless it starts prep +00:18 +566: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm prompt validation failure clears only non-start prompts +00:18 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish failure keeps the active schedule visible +00:18 +568: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: schedule events expose equality props for bloc deduping +00:19 +569: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:19 +570: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default marker when a scheduled day is not selected +00:19 +571: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default empty-day behavior when there are no events +00:19 +572: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:19 +573: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:19 +574: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:19 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:19 +576: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:19 +577: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:19 +578: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:19 +579: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: default dialog styles come from the active material theme +00:19 +580: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: showTwoActionDialog returns primary result +00:19 +581: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:20 +582: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:20 +583: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:20 +584: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:20 +585: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:20 +586: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:20 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:20 +588: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:20 +589: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:20 +590: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:20 +591: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:20 +592: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:21 +593: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:21 +594: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:21 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:21 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (tearDownAll) +00:21 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: (setUpAll) +00:21 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +597: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +598: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +599: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +600: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +601: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +602: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +603: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +604: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +605: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +606: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +607: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +608: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +609: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +610: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +611: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +612: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +613: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +614: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +615: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +616: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +617: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +618: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +619: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +620: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +621: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +622: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:23 +623: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:23 +624: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:23 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:23 +626: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:23 +627: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:23 +628: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:23 +629: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +630: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:24 +632: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:24 +633: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:24 +634: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +635: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +636: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +637: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +638: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +639: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +640: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +641: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +642: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +643: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +644: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +645: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +646: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +647: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +648: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +649: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +650: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:25 +651: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:25 +652: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:25 +653: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:25 +654: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:25 +655: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:25 +656: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:25 +657: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:25 +658: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:25 +659: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:25 +660: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:25 +661: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:25 +662: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:25 +663: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:26 +664: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:26 +665: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +666: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +667: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +668: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +669: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +670: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +671: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +672: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +673: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +674: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +675: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +676: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +677: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +678: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +679: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +680: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +681: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +682: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +683: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +684: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +685: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +686: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:27 +687: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:27 +688: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +689: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +690: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +691: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +692: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +693: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +694: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +695: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +696: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +697: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +698: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +699: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:28 +700: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:28 +701: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:28 +702: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:28 +703: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:28 +704: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:28 +705: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:28 +706: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:28 +707: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:29 +708: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:29 +709: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:29 +710: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +711: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +712: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +713: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +714: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +715: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +716: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +717: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +718: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +719: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +720: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +721: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +722: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +723: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +724: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +725: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +726: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +727: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +728: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +729: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +730: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +731: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +732: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:29 +733: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:30 +734: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:30 +735: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:30 +736: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:30 +737: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:30 +738: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:30 +739: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:30 +740: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:30 +741: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:30 +742: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: fills the bottom safe area while keyboard is hidden +00:30 +743: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:30 +744: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: paints a white backplate behind the iOS keyboard inset +00:30 +745: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: does not add keyboard backplate while keyboard is hidden +00:30 +746: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:30 +747: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +748: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +749: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +750: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +751: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +752: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +753: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +754: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +755: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +756: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +757: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +758: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +759: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +760: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +761: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +762: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: shows explicit choices for a repeated DST time +00:31 +763: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:31 +764: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:32 +765: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:32 +766: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:32 +767: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:32 +768: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +769: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +770: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +771: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +772: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +773: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +774: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:33 +775: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:33 +776: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:33 +777: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:33 +778: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +779: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +780: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +781: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +782: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +783: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +784: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +785: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +786: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +787: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +788: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:33 +789: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:34 +790: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:34 +791: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation errors explain invalid name and preparation time +00:34 +792: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +00:34 +793: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +00:34 +794: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +00:34 +795: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a15/generated_policy.txt b/plans/audit-2026-09-23/artifacts/a15/generated_policy.txt new file mode 100644 index 00000000..01eef09f --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a15/generated_policy.txt @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Generated Dart policy is aligned: outputs are ignored and untracked. diff --git a/plans/audit-2026-09-23/artifacts/a15/generation.txt b/plans/audit-2026-09-23/artifacts/a15/generation.txt new file mode 100644 index 00000000..c02e469d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a15/generation.txt @@ -0,0 +1,21 @@ +W These options have been removed and were ignored: --delete-conflicting-outputs + 0s freezed on 461 inputs; lib/core/backup/backup_crypto.dart + 0s freezed on 461 inputs: 1 skipped; lib/core/backup/backup_file_export_port.dart +W SDK language version 3.12.0 is newer than `analyzer` language version 3.10.0. Run `flutter packages upgrade`. + 6s freezed on 461 inputs: 3 skipped, 1 no-op; spent 5s analyzing; lib/core/backup/recurring_backup_data.dart + 7s freezed on 461 inputs: 278 skipped, 36 no-op; spent 6s analyzing; lib/presentation/shared/router/route_arguments.dart + 7s freezed on 461 inputs: 414 skipped, 47 no-op; spent 6s analyzing + 0s json_serializable on 922 inputs; lib/core/backup/backup_crypto.dart + 1s json_serializable on 922 inputs: 780 skipped, 142 no-op + 0s drift_dev on 3688 inputs; lib/core/backup/backup_crypto.dart + 0s drift_dev on 3688 inputs: 1572 skipped, 18 output, 266 same, 1 no-op; lib/core/backup/backup_service.dart.types.temp.dart + 0s drift_dev on 3688 inputs: 3262 skipped, 18 output, 266 same, 142 no-op + 0s source_gen:combining_builder on 1844 inputs; lib/core/backup/backup_crypto.dart + 0s source_gen:combining_builder on 1844 inputs: 1843 skipped, 1 no-op + 0s mockito:mockBuilder on 1040 inputs; test/core/backup/backup_crypto_test.dart + 0s mockito:mockBuilder on 1040 inputs: 974 skipped, 66 no-op + 0s injectable_generator:injectable_builder on 4728 inputs; lib/core/backup/backup_crypto.dart + 0s injectable_generator:injectable_builder on 4728 inputs: 4586 skipped, 30 same, 112 no-op + 0s injectable_generator:injectable_config_builder on 4728 inputs; lib/core/backup/backup_crypto.dart + 0s injectable_generator:injectable_config_builder on 4728 inputs: 4681 skipped, 47 no-op + Built with build_runner/aot in 10s; wrote 314 outputs. diff --git a/plans/audit-2026-09-23/artifacts/a15/initial_full_failure.txt b/plans/audit-2026-09-23/artifacts/a15/initial_full_failure.txt new file mode 100644 index 00000000..b36f54f6 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a15/initial_full_failure.txt @@ -0,0 +1,826 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:02 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:03 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:04 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:04 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:04 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:04 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:04 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:04 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:04 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:04 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:04 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:04 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: cancel preserves freshness and releases gate for a retry +00:04 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: previous extension-only filter fails before reaching iOS host +00:04 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:04 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:04 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:04 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:04 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:04 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:04 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:04 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:04 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:04 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:04 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:05 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:05 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:05 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:05 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:05 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:05 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:05 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:05 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:05 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:05 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:05 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:05 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:05 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:05 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:05 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:05 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:06 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:06 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:06 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:06 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:06 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:06 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:06 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:06 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:06 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: settings response is not proof of a grant: permission is reread +00:06 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:06 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:07 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:07 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real storage retains every failed platform ownership for the same Schedule +00:07 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:07 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=true +00:07 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: loadAll returns the current local registry +00:07 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: upsert replaces an existing schedule record and keeps others +00:07 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: deleteByScheduleId removes only the requested schedule +00:07 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: replaceAll deduplicates actual platform identity and deleteAll clears storage +00:07 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:07 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:07 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:07 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:07 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:07 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:07 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:07 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:07 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:07 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:07 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:07 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:07 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:07 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:07 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:07 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:07 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:07 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:07 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: malformed new content metadata never drops registry ownership records +00:07 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:07 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:07 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:07 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:07 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:07 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:07 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:07 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:07 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:07 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:07 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:07 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:07 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:07 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:08 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedule date ranges as start-inclusive and end-exclusive +00:08 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: an override cannot be moved into another occurrence preparation window +00:08 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: an override cannot be moved into another occurrence preparation window +00:08 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_data_source_contract_test.dart: schedule local data source contract does not expose domain entities +00:08 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:08 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:08 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:08 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:08 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:08 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:08 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:08 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:08 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:08 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:08 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:08 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:08 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:08 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: returns null for missing, corrupt, and incomplete session payloads +00:08 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:08 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:08 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:08 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:08 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getUserById returns null for an unknown user +00:08 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getAllUsers returns all persisted users as domain entities +00:08 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:09 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return an empty preparation list when user has no steps +00:09 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:09 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps when rows are out of order +00:09 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should keep remaining preparation steps when a link is broken +00:09 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return each preparation step once when links form a cycle +00:09 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should return one preparation step by id +00:09 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:09 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:09 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: updatePreparationUser should update a preparation step +00:09 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:09 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:09 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:09 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:09 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:09 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:09 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:09 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:09 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:09 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:09 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:09 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: updatePreparationUser should update a preparation step +00:09 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations createPreparationSchedule stores steps as an ordered linked list +00:09 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns empty for no steps +00:09 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId orders three linked steps when rows are out of order +00:09 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId keeps remaining steps when a link is broken +00:09 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns each step once when links form a cycle +00:09 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById returns the stored step contract +00:09 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: deleting a counted slot never extends or fills the series +00:09 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: last day is distinct from 31st, including leap February +00:09 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fifth weekday skips missing months while last weekday does not +00:09 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: weekly matching starts inclusively without adding the anchor day +00:09 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: daily intervals keep civil time and include the end date +00:09 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: spring gap is skipped and does not consume the count +00:09 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fall overlap requires an explicit first or second choice +00:09 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: new series starts at a future preparation and counts from there +00:09 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: range queries preserve original count and stable slot keys +00:09 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: unknown zone and exhausted search never report success +00:09 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: invalid and ambiguous rule definitions are rejected +00:09 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:09 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:09 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:09 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation +00:09 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:09 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:09 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: skips a just-missed alarm instead of scheduling catch-up +00:09 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:09 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:09 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:09 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:09 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:09 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:09 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:09 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:09 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:09 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:09 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:09 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:09 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:09 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:09 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:09 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:09 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:09 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:09 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:09 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:09 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:09 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:09 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:09 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:09 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:09 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:09 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:09 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:09 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:09 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:09 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:09 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:09 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:09 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:09 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:09 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:10 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:10 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:10 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:10 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:10 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:10 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:10 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:10 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:10 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:10 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:10 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:10 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:10 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure does not undo committed start or skip its snapshot +00:10 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure after finish still clears completed runtime +00:10 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:10 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:10 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:10 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:10 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:10 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:10 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:10 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:10 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:11 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:11 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:11 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:11 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:11 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:11 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:11 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:11 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart: unchanged preparation updates the schedule without saving preparation +00:11 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:11 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:11 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:11 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:11 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:11 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failure alone stops; a request from completion starts another pass +00:11 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:11 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:11 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: a registry-owned ID with a conflicting route is cancelled before repair +00:11 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=true +00:11 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=true +00:11 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=false +00:11 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=false +00:11 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native revoke and grant switch providers after cancelling the old registration +00:11 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:11 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:11 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:11 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:11 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:11 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:11 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:11 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: confirmed fallback orphans are targeted; unrelated and unknown ownership survive +00:11 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:11 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native failure uses only the actual fallback receipt after native cleanup +00:11 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed native write cleanup forbids fallback and keeps cancellation identity +00:12 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:12 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: off retains a cleanup warning and denial never becomes precise +00:12 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: no future registrations never becomes precise from capability +00:12 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: iOS alarm and notification language remains capability appropriate +00:12 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:12 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:12 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:12 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:12 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:12 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:12 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:12 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:12 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:12 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:12 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:12 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:12 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:12 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:12 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:12 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities cache fingerprint changes when preparation step name changes +00:12 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities cache fingerprint changes when preparation step name changes +00:12 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities schedule with preparation derives start time and total duration +00:12 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities combines a schedule entity with timed preparation preserving fields +00:12 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleEntity diagnostics string representation includes schedule identity for diagnostics +00:12 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleState timing helper durationUntilPreparationStartAt uses injected now deterministically +00:12 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:12 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:12 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:12 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:12 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:12 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:12 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:12 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:12 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:12 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:14 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (setUpAll) +00:14 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:16 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:16 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:16 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:16 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:17 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: view calendar button navigates to calendar screen +00:17 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: scheduled days render without hiding the selected day +00:17 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:17 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:17 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:17 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:17 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:18 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:18 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden for early-started schedule +00:18 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden for early-started schedule +00:18 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden for early-started schedule +00:18 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden for early-started schedule +00:18 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden for early-started schedule +00:18 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden for early-started schedule +00:18 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:18 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:18 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:18 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:18 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:18 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:18 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:18 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:18 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:19 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:19 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:19 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:19 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:19 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow step change notification fires for non-first transitions only once +00:19 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:19 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:19 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:19 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:19 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:19 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:19 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:19 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:19 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:19 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:19 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:19 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:19 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:19 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:19 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:19 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:19 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:19 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:19 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +549: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +550: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +551: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +552: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +553: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +554: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +555: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +556: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +557: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:20 +558: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:21 +559: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +560: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +561: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +562: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +563: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +564: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +565: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +566: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +568: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +569: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +570: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +571: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:21 +572: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:21 +573: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:21 +574: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:21 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:21 +576: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:21 +577: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:21 +578: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:21 +579: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:21 +580: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:21 +581: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:21 +582: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:21 +583: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/arc_indicator_test.dart: ArcIndicator paints background and progress arcs without throwing +00:21 +584: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:21 +585: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:21 +586: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: date picker saves the initial date value +00:22 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (setUpAll) +00:22 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: button styles resolve enabled and disabled visual contracts +00:22 +588: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme builds app calendar tokens and copies overrides +00:22 +589: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:22 +590: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:22 +591: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:22 +592: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:22 +593: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:22 +594: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:22 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (tearDownAll) +00:22 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: (setUpAll) +00:22 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +597: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +598: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +599: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +600: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +601: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +602: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +603: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +604: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +605: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +606: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +607: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +608: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:23 +609: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +610: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +611: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +612: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +613: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +614: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +615: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +616: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +617: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +618: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +619: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +620: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:24 +621: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:25 +622: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:25 +623: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:25 +624: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:25 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:25 +626: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:25 +627: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:25 +628: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:25 +629: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:25 +630: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:25 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:25 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: disabling schedule delivery cancels every local registration +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +The following TestFailure was thrown running a test: +Expected: <2> + Actual: <3> + +When the exception was thrown, this was the stack: +#4 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart:120:5) + +#5 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:15) + +#6 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1952:5) + + +(elided one frame from package:stack_trace) + +This was caught by the test expectation on the following line: + file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart line 120 +The test description was: + disabling schedule delivery cancels every local registration +════════════════════════════════════════════════════════════════════════════════════════════════════ +00:25 +631 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:25 +631 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: disabling schedule delivery cancels every local registration [E] + Test failed. See exception logs above. + The test description was: disabling schedule delivery cancels every local registration + +00:25 +632 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:25 +633 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:25 +634 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:25 +635 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:25 +636 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +637 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +638 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +639 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +640 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +641 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +642 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +643 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +644 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +645 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +646 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +647 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +648 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +649 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +650 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +651 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +652 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +653 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +654 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +655 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:26 +656 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:26 +657 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:27 +658 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:27 +659 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:27 +660 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:27 +661 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:27 +662 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:27 +663 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets close button confirms before leaving start prompt +00:27 +664 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets close button confirms before leaving start prompt +00:27 +665 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:27 +666 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:27 +667 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:27 +668 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:27 +669 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:27 +670 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:27 +671 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:27 +672 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:28 +673 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:28 +674 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:28 +675 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:28 +676 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:28 +677 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:28 +678 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:28 +679 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:28 +680 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:28 +681 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets active alarm close button navigates home without confirmation +00:28 +682 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:28 +683 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:28 +684 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:28 +685 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:28 +686 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:28 +687 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:28 +688 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:28 +689 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:28 +690 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:29 +691 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:29 +692 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:29 +693 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:29 +694 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:29 +695 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:29 +696 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:29 +697 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:29 +698 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:29 +699 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:29 +700 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:30 +701 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:30 +702 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:30 +703 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:30 +704 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:30 +705 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:30 +706 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:30 +707 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: following edit loads the series base and preserves an individual name override +00:30 +708 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +709 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +710 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +711 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +712 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +713 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +714 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +715 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +716 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +717 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +718 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +719 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +720 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +721 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +722 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:30 +723 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:31 +724 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:31 +725 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:31 +726 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:31 +727 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:31 +728 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:31 +729 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/input_models/backend_validation_input_models_test.dart: schedule name rejects blank and values over 30 chars +00:31 +730 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:31 +731 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:31 +732 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:31 +733 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:31 +734 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:31 +735 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:31 +736 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:31 +737 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:31 +738 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:31 +739 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: fills the bottom safe area while keyboard is hidden +00:32 +740 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +741 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +742 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +743 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +744 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +745 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +746 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +747 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +748 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +749 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +750 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +751 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +752 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +753 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:32 +754 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:33 +755 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:33 +756 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:33 +757 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:33 +758 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: shows explicit choices for a repeated DST time +00:33 +759 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: shows explicit choices for a repeated DST time +00:33 +760 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:33 +761 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:33 +762 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:33 +763 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:33 +764 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:33 +765 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edited name and place are submitted from form steps +00:33 +766 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: date time overlap keeps next disabled +00:33 +767 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:34 +768 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:34 +769 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:34 +770 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:35 +771 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:35 +772 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:35 +773 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:35 +774 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:35 +775 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:35 +776 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:35 +777 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:35 +778 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:35 +779 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:35 +780 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:35 +781 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:35 +782 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:35 +783 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:35 +784 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:35 +785 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:35 +786 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:36 +787 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation errors explain invalid name and preparation time +00:36 +788 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +00:36 +789 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +00:36 +790 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +00:36 +791 -1: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: disabling schedule delivery cancels every local registration diff --git a/plans/audit-2026-09-23/artifacts/a15/local_boundary.txt b/plans/audit-2026-09-23/artifacts/a15/local_boundary.txt new file mode 100644 index 00000000..71a9ce0a --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a15/local_boundary.txt @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Local-only product boundary verified. diff --git a/plans/audit-2026-09-23/artifacts/a15/ownership_failure.txt b/plans/audit-2026-09-23/artifacts/a15/ownership_failure.txt new file mode 100644 index 00000000..0c14c88a --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a15/ownership_failure.txt @@ -0,0 +1,76 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real storage retains every failed platform ownership for the same Schedule +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real storage retains every failed platform ownership for the same Schedule +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:00 +4 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:00 +4 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation [E] + Expected: NotificationTiming: + Actual: + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/domain/use-cases/reconcile_alarms_use_case_test.dart 412:5 main. + +00:00 +5 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:00 +6 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=true +00:00 +7 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=true +00:00 +8 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=true +00:00 +9 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:00 +10 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: loadAll returns the current local registry +00:00 +11 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:00 +12 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: upsert replaces an existing schedule record and keeps others +00:00 +13 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:00 +14 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:00 +15 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:00 +16 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:00 +17 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:00 +18 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:00 +19 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:00 +20 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:00 +21 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:00 +22 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:00 +23 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:00 +24 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:00 +25 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:00 +26 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:00 +27 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:00 +28 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:00 +29 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:00 +30 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:00 +31 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:00 +32 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:00 +33 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:00 +34 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:00 +35 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:00 +36 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:00 +37 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:00 +38 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:00 +39 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:00 +40 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:00 +41 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:00 +42 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:00 +43 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:00 +44 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:00 +45 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:00 +46 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:00 +47 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:00 +48 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:00 +49 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +50 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +51 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +52 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +53 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +54 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +55 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +56 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +57 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +58 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +59 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +60 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:01 +61 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: durable reset intent blocks writers after cancellation failure without deleting data +00:01 +62 -1: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation diff --git a/plans/audit-2026-09-23/artifacts/a15/red.txt b/plans/audit-2026-09-23/artifacts/a15/red.txt new file mode 100644 index 00000000..2cf1c983 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a15/red.txt @@ -0,0 +1,15 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart +00:00 +0: request after schedule snapshot receives its own latest pass +00:00 +0 -1: request after schedule snapshot receives its own latest pass [E] + Expected: ['committed-after-snapshot'] + Actual: [] + Which: at location [0] is [] which shorter than expected + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/domain/use-cases/alarm_reconciliation_concurrency_test.dart 62:5 main. + +00:00 +0 -1: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: request after schedule snapshot receives its own latest pass diff --git a/plans/audit-2026-09-23/issues/A14.md b/plans/audit-2026-09-23/issues/A14.md index 6cb29cb9..6ae97f5f 100644 --- a/plans/audit-2026-09-23/issues/A14.md +++ b/plans/audit-2026-09-23/issues/A14.md @@ -238,4 +238,4 @@ A13 커밋 `59d92bdd` 이후 구현했으며, 현재 기반 HEAD는 `e2fbdf0bb23 head `b81259bf66881289bf6bca9ac7cca2548de6a326`의 [Flutter run35887794993](https://github.com/DevKor-github/OnTime-front/actions/runs/35887794993)은 전체772 tests/analyze 및 coverage87.44%(11487/13137)를 통과했다. [Android run35887794956](https://github.com/DevKor-github/OnTime-front/actions/runs/35887794956)도 release APK/manifest를 통과했다. artifact10763053548, synthetic merge3ebcf19237dcdd87e262c176762149dfd7ef0e8b, APK SHA2567e88d6882e3d1060965b33e7d6bab12e04f5aff3a4be5448e1ec859f8c314b60을 read-back했다. receipt는 a14-ci-validation.json이다. -이후 A15 통합 리뷰에서 실제 AlarmRegistryRepositoryImpl.replaceAll이 scheduleId로 dedup하는 경계를 확인했다. 같은 일정에 서로 다른 provider/실제 platform ID의 취소 ownership이 여러 개 있으면 하나를 잃을 수 있다. 기존 fake registry 중심 회귀와772개 통과가 이 실제 persistence 문제를 검증한 것은 아니다. A15 #594에서 플랫폼 소유 ID 기준 보존, 동일 일정의 keeper 외 취소·실패 보존, 실제 repo/data source 왕복 회귀를 구현 중이며 아직 통과·완료로 기록하지 않는다. 이 후속 통합과 실기기 조건이 남아 A14 이슈는 OPEN으로 유지한다. +이후 A15 통합 리뷰에서 실제 AlarmRegistryRepositoryImpl.replaceAll이 scheduleId로 dedup하는 경계를 확인했다. 같은 일정에 서로 다른 provider/실제 platform ID의 취소 ownership이 여러 개 있으면 하나를 잃을 수 있다. 기존 fake registry 중심 회귀와772개 통과가 이 실제 persistence 문제를 검증한 것은 아니다. A15 #594의 후속 구현은 플랫폼 소유 ID 기준 보존, 동일 일정의 keeper 외 취소·실패 보존, 실제 repo/data source와 SharedPreferences mock backend 왕복 회귀를 포함한다. Root가 최종37 source/test 파일의 검증 전후·현재 SHA256 일치 및 전체795 tests/analyze 통과를 확인했다. cancellationPending 최소 record에서도 실제 notificationTiming enum을 보존한다. 이 후속 로컬 검증은 실제 기기 디스크/OS 전달 증거가 아니며 새 커밋 원격 CI를 별도로 확인한다. 실기기 조건이 남아 A14 이슈는 OPEN으로 유지한다. diff --git a/plans/audit-2026-09-23/issues/A15.md b/plans/audit-2026-09-23/issues/A15.md index 6c752b33..a796be7f 100644 --- a/plans/audit-2026-09-23/issues/A15.md +++ b/plans/audit-2026-09-23/issues/A15.md @@ -6,7 +6,7 @@ Backup Restore/Local Data Reset과 겹치면 영향이 더 크다. registry 저장 직전에 세대만 확인하는 것으로는 이미 OS에 보낸 구세대 예약이 데이터 교체 이후 늦게 성공하는 것을 막지 못한다. 새 데이터에 같은 일정 ID가 있으면 구세대 정리가 신세대 예약을 취소하는 역방향 경합도 생긴다. -감사 판정은 **P1 · 경쟁 조건 확인, 결정적 재현 필요**다. 이 문서는 읽기 전용 조사와 실제 grill 문답으로 확정한 구현 명세다. 현재 A15 구현·자동 테스트·실기기 재현 완료를 주장하지 않는다. +감사 판정은 **P1 · 경쟁 조건 확인, 결정적 재현 필요**다. 아래 초기 명세는 읽기 전용 조사와 실제 grill 문답으로 확정했다. 후속 구현과 최종 로컬795 tests/analyze 검증은 하단 실행 기록에 추가했다. 원격 CI와 실제 Android/iOS 검증은 별도 확인하며 실기기 완료를 주장하지 않는다. ## 조사 기준과 코드 근거 @@ -135,7 +135,7 @@ upstream recurrence 규칙·DB schema2·backup v2를 유지한다. nearest eligi | U03 | 저장 성공/OS 적용 실패를 구별하는 결과를 제공한다. 일반 설정 UI의 전면 재설계는 제외한다. | | upstream 반복 일정 | 회차 materialize·가장 가까운 60개·schema2·backup v2를 보존한다. 재조정 자체가 무한 재실행을 만들지 않게 검증한다. | -읽기 전용 조사와 이 문서만 작성했다. 소스, 공유 state.json, GitHub, 커밋을 변경하지 않았다. 구현 및 검증 결과는 후속 승인된 실행의 실제 SHA·명령·결과로 추가해야 한다. +초기 grill 단계에서는 읽기 전용 조사와 이 문서만 작성했다. 후속 구현 및 검증 결과는 하단 실행 기록과 validation receipt에 구분해 추가했다. ## grill-with-docs 실제 전체 문답 @@ -184,3 +184,54 @@ upstream recurrence 규칙·DB schema2·backup v2를 유지한다. nearest eligi **Root 답변** > Q4 답변: 확정합니다. request revision은 메모리상의 접수 순번이며 백업용 durable dataRevision 또는 DB schema version과 별개입니다. 알림 관측/registry 변경, 회차 materialize가 스스로 또 reconcile을 요청하는 전역 watcher는 만들지 않습니다. 열거한 실제 제품 mutation/lifecycle 경로를 감사해 commit 뒤 요청이 필요한 곳만 연결하고, default/custom preparation 편집은 현재 미래 일정의 준비 시작 시각/내용에 실제 영향을 주는 경우에만 포함합니다. 템플릿 라이브러리 같은 무관 변경은 확장하지 않습니다. locale/timezone/permission은 기존 refresh 진입점에서 현재 값을 다시 읽고, 새 periodic polling은 추가하지 않습니다. upstream recurrence 규칙·schema2·backup v2를 유지하며 nearest eligible 60개 대상 교체와 반복 회차 materialize가 drain의 무한 self-dirty를 일으키지 않음을 회귀로 확인하세요. 하나의 pass 안 snapshot의 transaction 일관성 자체가 별도 결함이면 A08/C01과 경계를 표시하되, committed mutation 뒤 실제 새 요청이 접수되면 다음 pass가 최신 상태를 반영한다는 A15의 약속은 보장합니다. 이제 실제 전체 질문·답변을 생략 없이 포함한 상세 문서 작성해 주세요. + + +## 구현 결과 — 2026-09-24 + +후속 구현은 root가 A14 소스 커밋 `282db21f5effa86eb2f82c81c247d57a0a6acfd0` 확정 뒤 명시적으로 허용했다. 위 문답과 계획은 원문 그대로 보존한다. 현재 구현 기반 HEAD는 `d2ae69d3f0e139d2d88c46a7f1e0b8da5e403830`이며 A15 소스 커밋과 GitHub 게시·read-back은 root가 별도로 수행한다. + +### 적용 내용 + +- `ReconcileAlarmsUseCase.call()`은 await 전에 메모리 request revision과 대기자를 접수한다. 단일 drain은 pass 시작 cutoff까지만 결과/오류로 완료하고, 도중 접수된 요청은 최신 DB를 읽는 다음 pass로 처리한다. 실패 자체는 재실행 원인이 아니며, cutoff 완료 뒤 idle 전환 사이에는 await가 없다. +- `AlarmOperationCoordinator`가 reconcile, 표적/전체 취소, 개인정보 migration, 복원·초기화 cleanup의 단일 알림 writer다. 기존 gate generation을 사용하고 export의 단순 busy와 실제 replacement를 구분한다. replacement가 구세대 Future를 typed invalidated로 끝내더라도 이미 전송한 native Future가 반환할 때까지 writer 소유권은 유지한다. 호출자의 timeout은 원래 OS Future를 취소하지 않는다. +- OS side effect 전에 최소 ownership을 메모리에 남기고 저장을 시도한다. 취소 실패/저장 실패 기록은 raw 제목·본문·fingerprint 없이 실제 provider/플랫폼 ID와 필요한 시각·timing enum만 유지한다. 성공한 최종 registry write가 메모리 보조 기록을 해제한다. 이는 프로세스 내 보호이며 영속 crash journal이 아니다. +- 취소는 A14의 실제 플랫폼 취소/read-back 계약을 재사용한다. `AlarmRegistrationCleanup`은 취소 미완료를 `AlarmCleanupIncomplete`로 전달하고 tombstone을 보존한다. 실제 저장소 replaceAll/upsert도 Schedule ID 하나로 덮어쓰지 않도록 실제 플랫폼 identity로 dedup한다. fallback은 notification ID, Android native는 requestCode, iOS AlarmKit은 플랫폼의 결정적 UUID 입력인 Schedule ID를 provider와 조합한다. 서로 다른 Schedule 라벨이 동일 OS ID를 가리키면 별도 실예약 두 개로 세지 않는다. +- 한 Schedule에 복수 실예약 기록이 있으면 유지 가능한 하나를 선택하고 나머지 소유권은 취소한다. 실패한 중복은 조용히 버리지 않으며 keeper와 tombstone을 함께 남기고 armed 성공으로 보고하지 않는다. +- 실제 `BackupService.applyRestore`는 replacement gate 안에서 기존 OS 작업 종료와 cleanup을 기다린 뒤 DB transaction을 수행한다. cleanup 실패 시 기존 DB가 유지되고 gate가 다시 사용 가능해져 명시적 restore 재시도가 가능하다. 일반 drain이 gate 해제를 기다리는 교착을 만들지 않는다. +- 실제 `LocalDataResetService.reset`은 reset marker 저장 후 즉시 일반 writer를 무효화한다. 취소 오류를 삼켜 DB close/delete·prefs clear·키 삭제로 진행하지 않는다. marker 이후 실패하면 기존 DB와 취소 증거는 남지만 같은 프로세스의 일반 사용과 reset 재시도는 차단된다. 현 정책상 재시작 복구가 필요하며 D03 전체 복구 프로토콜 완료를 의미하지 않는다. +- 일정 mutation coordinator는 DB commit 뒤 요청을 먼저 접수하고 targeted cancellation 오류를 관찰한다. 실제 default/custom preparation 저장 use case에도 후속 요청을 연결했다. 준비 시작/완료의 직접 cancel 호출은 취소 실패로 이미 commit한 DB 사실과 runtime snapshot/정리를 취소하지 않는다. 구세대 invalidated이면 신세대 runtime을 건드리지 않는다. +- startup/resume와 설정 호출부의 fire-and-forget 오류를 관찰한다. MyPage는 저장된 설정을 유지하면서 예약/취소 불완전 상태를 기존 문구로 표시한다. 전역 watcher, 주기 polling, 새 서버, schema/backup 형식 변경은 없다. + +### Root 후속 리뷰와 보완 + +1. **실제 registry gap**: A14 fake registry만으로는 `AlarmRegistryRepositoryImpl.replaceAll`의 Schedule ID 단일 dedup 유실을 잡지 못했다. 이번에는 실제 SharedPreferences local data source 왕복으로 native와 fallback 두 ID 보존, upsert, 동일 OS ID dedup을 검증했다. reconcile의 기존 단일 map도 keeper 외 소유권을 지우지 않게 보완하고 취소 성공/실패 양쪽을 검증했다. +2. **reset 실패 경계**: 기존 `_cancelAllAlarms().catchError`와 그 뒤 prefs clear가 취소 증거를 지우는 경로를 제거했다. marker 후 gate가 일반 상태로 열리지 않도록 invalidate 시점을 옮기고, 실제 서비스/Drift/secure-store adapter로 원본과 marker·prefs·키 보존 및 일반 writer 차단을 검증했다. +3. **직접 준비 시작/완료 호출자**: cancel이 이제 오류를 전달하므로 DB 시작/완료 뒤 runtime 저장/정리가 중단되는 새 회귀가 없도록 직접 use case 테스트를 추가했다. durable DB 성공을 알림 성공과 혼동하지 않는다. +4. **실제 lifecycle 통합**: 기존 backup/UI 테스트의 `NoopAlarmCleanup`은 격리용 adapter일 뿐 통합 증거로 세지 않았다. 별도 `alarm_replacement_integration_test.dart`는 실제 BackupService/ResetService 및 Drift DB를 쓰고 OS만 blocking fake로 대체했다. 미반환 OS Future 동안 DB 내용이 불변이고 실제 반환·취소 후에만 복원이 교체함을 확인한다. +5. **actual mutation 접수 증거**: 실제 create/update/preparation 테스트에서 저장 뒤 테스트 자체의 추가 reconcile 호출이 접수 누락을 가릴 수 있음을 최종 검토에서 발견했다. 그 호출을 제거하고 use case가 스스로 접수한 pass만 직렬 owner barrier로 기다리도록 강화했다. snapshot 중 삭제 coordinator 테스트도 동일하게 보완했다. 제품 소스는 이 보강 동안 변경하지 않았다. + +### 검증 범위와 증거 + +최종 동결 소스에서 `flutter test --reporter expanded` **795개 통과**, `flutter analyze` **No issues found**를 확인했다. 검증 직전·직후 37파일 hash map은 일치하며 aggregate는 `de887b4e5d34a0ac9c37d62e92a652128fabde3ae1ddcb6dc54d6eccff446dd2`다. generated Dart policy, local-only boundary, `git diff --check`도 통과했다. + +원시 로그와 변경 37개 Dart 파일의 전체 SHA-256, 검증 직전·직후 aggregate, 명령별 결과는 `plans/audit-2026-09-23/a15-validation.json` 및 `plans/audit-2026-09-23/artifacts/a15/`에 기록한다. 코드 생성 결과는 로컬에서 재생성했고 ignored/untracked 상태를 검증한다. + +| 검증 종류 | 실제 검증한 경계 | +|---|---| +| 최초 RED | 기존 coalescing에서 snapshot 뒤 접수한 호출이 `committed-after-snapshot` 대신 빈 결과를 반환함 | +| barrier/Completer | cutoff별 독립 완료, 3개 연속 pass, 실패+새 요청, 실패 단독 종료, 완료 callback 새 요청, typed invalidation, 늦은 native와 같은 ID 재사용, timeout이 소유권을 풀지 않음, export busy | +| 실제 registry | SharedPreferences 왕복 복수 provider/ID, upsert, 같은 OS ID dedup, 동일 Schedule keeper와 중복 취소/실패 보존, tombstone timing 유지 | +| 실제 restore/reset | Drift 원본/복원 내용, 미반환 OS Future, 취소 실패 뒤 DB/prefs/key/marker/gate 상태, restore 재시도, reset 재시작 필요 상태 | +| 실제 mutation/반복 | create/update와 preparation 저장 use case가 자동으로 접수한 trailing pass, 실제 Drift/반복 materialize와 nearest eligible 60개 교체, self-dirty 반복 없음 | +| 기존/추가 회귀 | cancel 호출자, 준비 시작/완료 snapshot, privacy migration, 상세알림 OFF, MyPage 저장 성공·알림 실패 표시 및 기존 전체 suite | + +진행 중 발견된 실패도 남긴다. 최초 전체 실행은 791개 통과/1개 실패였으며 새 저장 후 접수 때문에 늘어난 reconcile 횟수를 정확히 2로 고정한 UI assertion이 원인이었다. 해당 구현 횟수 assertion을 제거하고 실제 설정·취소·상태 표시 assertion을 유지했다. 최소 ownership 전환 후 별도 실행은 62개 통과/1개 실패였으며 cancellation tombstone의 `notificationTiming` 유실을 드러냈다. 기대를 지우지 않고 `ownershipOnly`와 실제 local source sanitizer가 timing enum을 보존하도록 수정하고 실제 왕복 assertion을 추가했다. 최종 mutation fixture는 저장된 civil-time 해석을 명확히 하기 위해 `occurrenceOffsetSeconds: 0`을 지정했다. + +### 남은 경계 + +- 실제 Android/iOS 권한 변경, 잠금 화면, Doze, 재부팅, 도착 시각·중복 개수 검증은 **미실행/device pending**이다. Dart fake와 mocked platform 계약 통과는 OS 전달 증거가 아니다. +- A15는 native 소스를 변경하지 않았다. 이번 검증에서 native SDK 컴파일/APK/Runner build를 다시 실행했다고 주장하지 않는다. A14의 별도 SDK/CI 증거는 root의 해당 receipt를 따른다. +- native Future가 영구 미반환이면 writer/restore cleanup도 대기한다. timeout을 가짜 취소로 취급해 동일 ID를 재사용하지 않는다. +- registry와 메모리 모두 사라지는 프로세스 사망, interrupted reset bootstrap 전체, persistent crash journal은 D03 의존이다. 손상 DB manifest/복구 계약은 D02 의존이며 구현하지 않았다. +- 한 pass 안의 여러 DB 읽기 transaction snapshot 원자성은 A08/C01 경계다. 새로운 commit 뒤 실제 접수된 요청이 다음 pass에 반영된다는 A15 보장을 검증했다. +- 일반 설정 저장 경합의 전면 재설계는 U03 범위다. 이번 변경은 저장 성공을 알림 실패로 롤백하지 않고 관찰 가능한 실패를 남기는 최소 통합이다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index c9eb476a..d64bdc1c 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -290,7 +290,7 @@ "`reconcile_alarms_use_case.dart:204-230,529-538`; `my_page_screen.dart:203-214`", "permission grant/revoke·OS 예약 유실·재시작 후 provider와 OS pending/registry를 일치시킴. 불일치 시 취소·교체·정직한 상태 표시." ], - "status": "code_verified_followup_pending", + "status": "code_verified_device_pending", "agent": "/root/grill_a14", "issue_number": 593, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/593", @@ -300,16 +300,17 @@ "verification": [ "Frozen source24 SHA256 matched; focused100/full772, analyzer, native Swift SDK/Kotlin compile, generated/local-only checks passed. a14-validation.json.", "Root visually reviewed actual MyPage standalone KO/EN captures; fake receipts/in-memory DB, not physical OS screenshots.", - "Remote b81259bf: Flutter772/analyze, coverage87.44% (11487/13137), Android APK/manifest passed. a14-ci-validation.json." + "Remote b81259bf: Flutter772/analyze, coverage87.44% (11487/13137), Android APK/manifest passed. a14-ci-validation.json.", + "A15 followup local795/analyze and actual registry/data source ownership roundtrip passed; final remote CI separately pending" ], "blockers": [ - "A15 integration follow-up: actual registry scheduleId dedup can lose multiple provider/platform-ID cancellation ownership; implementation underway.", - "Actual Android/iOS delivery and permission/Doze/reboot matrix not executed." + "Actual Android/iOS delivery and permission/Doze/reboot matrix not executed.", + "A15 ownership followup local verification passed; final remote CI pending." ], "sequence": 9, "issue_body_sha256": "23769c09503d629f813df5b93db7b2b6fce715dd15ea1571ea09fa06b9c1a5d4", "readback_verified": true, - "body_sha256": "562f5779035405a4a284ba08f76067b19c50eddf6050aa6d5f47093ad3a4c470" + "body_sha256": "5efdc7fd7cc9debee0f5f7fed3f5146d346eb8ca52f9286b41960f750304b888" }, { "audit_id": "A15", @@ -322,18 +323,27 @@ "`reconcile_alarms_use_case.dart:43-57`; `schedule_mutation_alarm_effects_coordinator.dart:37-52`", "dirty flag/data revision 기반 재실행 또는 직렬 작업 큐. snapshot 읽기 뒤 생성·수정·삭제를 끼워 넣어 최종 OS 예약이 최신 DB를 반영하는지 검사. reset/cancel과 경계 공유." ], - "status": "implementing", + "status": "code_verified_device_pending", "agent": "/root/grill_a15", "issue_number": 594, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/594", "decisions_file": "plans/audit-2026-09-23/issues/A15.md", "implementation_commit": null, - "pr_url": null, - "verification": [], - "blockers": [], + "pr_url": "https://github.com/DevKor-github/OnTime-front/pull/585", + "verification": [ + "Deterministic post-snapshot request regression failed before fix; trailing pass now covers each accepted cutoff", + "Final frozen source: 795 Flutter tests and analyzer passed; root independently compared 37 before/after/current source hashes", + "Actual BackupService/Reset + Drift integration preserves replacement ordering and pending reset safety; fake OS and mock secure storage are not device evidence", + "Actual CRUD/preparation/recurring usecases trigger trailing reconciliation without test-injected extra calls; nearest60 and no self-dirty loop checked", + "Actual registry repository/data source and SharedPreferences mock backend retain multiple platform ownerships and cancellation timing enum" + ], + "blockers": [ + "Real Android/iOS concurrent edits, restore/reset and delivery verification pending", + "Remote CI for final A15 commit pending; D03 durable crash journal and A09 full restore runtime cleanup remain separate" + ], "sequence": 10, "readback_verified": true, - "body_sha256": "a565fc9d7133fb725312f7d10e5601e480c04b71fcc6dc91b8c98c5b75485179" + "body_sha256": "016d79f9ab44a022c92cbab9237e47515fe5e0f8a643b9501d471e46cde453d9" }, { "audit_id": "D03", diff --git a/test/core/backup/backup_restore_picker_test.dart b/test/core/backup/backup_restore_picker_test.dart index f84a36cf..c824cc4f 100644 --- a/test/core/backup/backup_restore_picker_test.dart +++ b/test/core/backup/backup_restore_picker_test.dart @@ -1,3 +1,4 @@ +import '../../helpers/noop_alarm_cleanup.dart'; import 'dart:io'; import 'dart:typed_data'; @@ -50,6 +51,7 @@ void main() { service = BackupService( database, _Metadata(), + NoopAlarmCleanup(), crypto: BackupCrypto(sodiumLoader: loadSodiumForTest), ); await database.userDao.putUser( diff --git a/test/core/backup/backup_service_test.dart b/test/core/backup/backup_service_test.dart index 7faebee4..76774736 100644 --- a/test/core/backup/backup_service_test.dart +++ b/test/core/backup/backup_service_test.dart @@ -1,3 +1,4 @@ +import '../../helpers/noop_alarm_cleanup.dart'; import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; @@ -37,6 +38,7 @@ void main() { service = BackupService( database, _MetadataProvider(), + NoopAlarmCleanup(), crypto: BackupCrypto(sodiumLoader: loadSodiumForTest), exportPort: exportPort, operationGate: gate, diff --git a/test/core/database/alarm_replacement_integration_test.dart b/test/core/database/alarm_replacement_integration_test.dart new file mode 100644 index 00000000..9b1d8cd0 --- /dev/null +++ b/test/core/database/alarm_replacement_integration_test.dart @@ -0,0 +1,195 @@ +import 'package:drift/drift.dart' show Value; +import 'package:drift/native.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/backup/backup_crypto.dart'; +import 'package:on_time_front/core/backup/backup_service.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/core/database/installation_key_store.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/database/local_data_reset_service.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/app_metadata_service.dart'; +import 'package:on_time_front/domain/entities/preparation_entity.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../domain/use-cases/alarm_reconciliation_concurrency_test.dart' + as concurrency; +import '../../helpers/sodium_test_loader.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late AppDatabase database; + setUp(() async { + SharedPreferences.setMockInitialValues({'sentinel': 'keep'}); + FlutterSecureStorage.setMockInitialValues({'key-sentinel': 'keep'}); + database = AppDatabase.forTesting(NativeDatabase.memory()); + await database.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: 'backup value', + ), + ); + await database.preparationUserDao.createPreparationUser( + const PreparationEntity(preparationStepList: []), + 'local-profile', + ); + }); + tearDown(() => database.close()); + + BackupService backup(concurrency.Rig r) => BackupService( + database, + _Metadata(), + r.cancelAll, + operationGate: r.gate, + crypto: BackupCrypto(sodiumLoader: loadSodiumForTest), + ); + Future candidate(BackupService service) async => + service.previewEncryptedBackup( + await service.createEncryptedBackup('replacement fixture password'), + 'replacement fixture password', + ); + + test( + 'actual restore waits for late platform completion before replacing database', + () async { + final native = concurrency.BlockingNative(); + final r = concurrency.Rig(native: native); + addTearDown(r.dispose); + final service = backup(r); + final restore = await candidate(service); + await database + .update(database.users) + .write(const UsersCompanion(note: Value('current value'))); + r.repository.schedules = [r.schedule('old')]; + final invalidated = expectLater( + r.reconcile(), + throwsA(isA()), + ); + await native.entered.future; + var complete = false; + final pending = service.applyRestore(restore).then((_) { + complete = true; + }); + await invalidated; + await pumpEventQueue(); + expect(complete, false); + expect( + (await database.select(database.users).getSingle()).note, + 'current value', + ); + native.release.complete(); + await pending; + expect(native.events, ['schedule:old', 'cancel:old']); + expect( + (await database.select(database.users).getSingle()).note, + 'backup value', + ); + expect(r.gate.isAvailable, true); + }, + ); + + test( + 'actual restore cancellation failure preserves original database and ownership', + () async { + final r = concurrency.Rig(); + addTearDown(r.dispose); + final service = backup(r); + final restore = await candidate(service); + await database + .update(database.users) + .write(const UsersCompanion(note: Value('current value'))); + r.repository.schedules = [r.schedule('old')]; + await r.reconcile(); + r.fallback.throwOnCancelIds.add('old'); + await expectLater( + service.applyRestore(restore), + throwsA(isA()), + ); + expect( + (await database.select(database.users).getSingle()).note, + 'current value', + ); + expect(r.registry.records.single.cancellationPending, true); + expect(r.gate.isAvailable, true); + r.fallback.throwOnCancelIds.clear(); + await service.applyRestore(restore); + expect( + (await database.select(database.users).getSingle()).note, + 'backup value', + ); + }, + ); + + test( + 'durable reset intent blocks writers after cancellation failure without deleting data', + () async { + final native = concurrency.BlockingNative()..throwOnCancelIds.add('old'); + final r = concurrency.Rig(native: native); + addTearDown(r.dispose); + final keys = _Keys(); + final service = LocalDataResetService( + database, + keys, + r.cancelAll, + operationGate: r.gate, + ); + r.repository.schedules = [r.schedule('old')]; + final invalidated = expectLater( + r.reconcile(), + throwsA(isA()), + ); + await native.entered.future; + final pending = service.reset(); + final failed = expectLater( + pending, + throwsA(isA()), + ); + await invalidated; + await pumpEventQueue(); + expect(keys.deleted, false); + expect( + (await database.select(database.users).getSingle()).note, + 'backup value', + ); + native.release.complete(); + await failed; + expect(r.gate.isInvalidated, true); + expect(keys.deleted, false); + expect( + (await SharedPreferences.getInstance()).getString('sentinel'), + 'keep', + ); + expect( + await const FlutterSecureStorage().read( + key: 'ontime_local_reset_pending_v1', + ), + 'pending', + ); + expect(r.registry.records.single.cancellationPending, true); + await expectLater( + r.reconcile(), + throwsA(isA()), + ); + await expectLater(service.reset(), throwsA(isA())); + // Existing policy: restart resumes the pending reset; no ordinary retry or + // registry wipe can silently reopen this installation in the same process. + }, + ); +} + +class _Keys extends InstallationKeyStore { + bool deleted = false; + @override + Future delete() async { + deleted = true; + } +} + +class _Metadata implements AppMetadataProvider { + @override + Future getMetadata() async => + const AppMetadata(version: 'test', buildNumber: '1'); +} diff --git a/test/data/repositories/alarm_registry_repository_impl_test.dart b/test/data/repositories/alarm_registry_repository_impl_test.dart index 034c6b36..d3d8eb8b 100644 --- a/test/data/repositories/alarm_registry_repository_impl_test.dart +++ b/test/data/repositories/alarm_registry_repository_impl_test.dart @@ -1,9 +1,13 @@ +import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; +import '../../domain/use-cases/reconcile_alarms_use_case_test.dart' as fixtures; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/data/data_sources/alarm_registry_local_data_source.dart'; import 'package:on_time_front/data/repositories/alarm_registry_repository_impl.dart'; import 'package:on_time_front/domain/entities/alarm_entities.dart'; void main() { + TestWidgetsFlutterBinding.ensureInitialized(); late _FakeAlarmRegistryLocalDataSource localDataSource; late AlarmRegistryRepositoryImpl repository; @@ -12,6 +16,121 @@ void main() { repository = AlarmRegistryRepositoryImpl(localDataSource: localDataSource); }); + test( + 'real storage retains every failed platform ownership for the same Schedule', + () async { + SharedPreferences.setMockInitialValues({}); + final real = AlarmRegistryRepositoryImpl( + localDataSource: AlarmRegistryLocalDataSourceImpl(), + ); + final native = _record( + 'same', + ).copyWith(nativeAlarmId: 7, cancellationPending: true); + final first = _record('same').copyWith( + provider: AlarmProvider.localNotification, + fallbackNotificationId: 11, + notificationTiming: NotificationTiming.approximate, + cancellationPending: true, + ); + final second = first.copyWith(fallbackNotificationId: 12); + await real.replaceAll([native, first, second]); + final restored = await real.loadAll(); + expect( + restored + .singleWhere((r) => r.fallbackNotificationId == 11) + .notificationTiming, + NotificationTiming.approximate, + ); + expect(restored.map(alarmOwnershipKey).toSet(), { + 'androidAlarmManager:7', + 'localNotification:11', + 'localNotification:12', + }); + await real.upsert(second); + expect( + (await real.loadAll()).map(alarmOwnershipKey).toSet(), + restored.map(alarmOwnershipKey).toSet(), + ); + // Different Schedule labels do not make the same actual OS ID two requests. + await real.upsert( + _record('different').copyWith( + provider: AlarmProvider.localNotification, + fallbackNotificationId: 11, + cancellationPending: true, + ), + ); + final after = await real.loadAll(); + expect(after, hasLength(3)); + expect( + after.singleWhere((r) => r.fallbackNotificationId == 11).scheduleId, + 'different', + ); + }, + ); + + for (final failDuplicateCancellation in [false, true]) { + test( + 'real registry preserves duplicate ownership until confirmed cancellation: failure=$failDuplicateCancellation', + () async { + SharedPreferences.setMockInitialValues({}); + final real = AlarmRegistryRepositoryImpl( + localDataSource: AlarmRegistryLocalDataSourceImpl(), + ); + final now = DateTime.utc(2026, 9, 24); + final schedule = fixtures.scheduleWithAlarmAt( + id: 'same', + alarmTime: now.add(const Duration(hours: 1)), + ); + final first = buildScheduledAlarmRecord( + schedule, + alarmOffset: const Duration(minutes: 5), + provider: AlarmProvider.localNotification, + currentTimeZoneId: 'UTC', + languageCode: 'en', + ).copyWith(fallbackNotificationId: 11); + final second = first.copyWith(fallbackNotificationId: 12); + final fallback = fixtures.FakeFallbackAlarmNotificationService() + ..permission = AlarmPermissionState.granted; + await fallback.scheduleFallbackAlarm(first); + await fallback.scheduleFallbackAlarm(second); + await real.replaceAll([first, second]); + final alarms = fixtures.FakeAlarmRepository()..schedules = [schedule]; + if (failDuplicateCancellation) fallback.throwOnCancelIds.add('same'); + final reconcile = ReconcileAlarmsUseCase.test( + alarms, + real, + fixtures.FakeAlarmSchedulerService(), + fallback, + nowProvider: () => now, + timeZoneProvider: () async => 'UTC', + languageCodeProvider: () => 'en', + ); + final result = await reconcile(); + if (failDuplicateCancellation) { + expect(result.armedScheduleIds, isEmpty); + expect(result.status, AlarmReconciliationStatus.partial); + final kept = await real.loadAll(); + expect(kept.map((r) => r.fallbackNotificationId).toSet(), {11, 12}); + expect( + kept + .singleWhere((r) => r.fallbackNotificationId == 12) + .cancellationPending, + true, + ); + fallback.throwOnCancelIds.clear(); + expect((await reconcile()).armedScheduleIds, ['same']); + } else { + expect(result.armedScheduleIds, ['same']); + } + expect((await real.loadAll()).single.fallbackNotificationId, 11); + expect(fallback.pendingFallback.keys, ['11']); + expect(fallback.canceledFallback.map((r) => r.fallbackNotificationId), [ + 12, + ]); + }, + ); + } + test('loadAll returns the current local registry', () async { localDataSource.records = [_record('schedule-1')]; @@ -44,7 +163,7 @@ void main() { }); test( - 'replaceAll deduplicates by schedule id and deleteAll clears storage', + 'replaceAll deduplicates actual platform identity and deleteAll clears storage', () async { await repository.replaceAll([ _record('schedule-1', title: 'Old'), diff --git a/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart b/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart new file mode 100644 index 00000000..a9a41502 --- /dev/null +++ b/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart @@ -0,0 +1,431 @@ +import 'dart:async'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/domain/use-cases/cancel_all_alarms_use_case.dart'; +import 'package:on_time_front/domain/use-cases/cancel_schedule_alarm_use_case.dart'; +import 'package:on_time_front/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/domain/entities/schedule_with_preparation_entity.dart'; +import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; + +import 'reconcile_alarms_use_case_test.dart' as fixtures; + +class BlockingAlarmRepository extends fixtures.FakeAlarmRepository { + final snapshots = >{}; + final releases = >{}; + final failedPasses = {}; + + void block(int pass) { + snapshots[pass] = Completer(); + releases[pass] = Completer(); + } + + @override + Future> getAlarmWindow( + DateTime startDate, + DateTime endDate, + ) async { + final snapshot = List.of( + await super.getAlarmWindow(startDate, endDate), + ); + final pass = alarmWindowRequestCount; + snapshots[pass]?.complete(); + await releases[pass]?.future; + if (failedPasses.contains(pass)) throw StateError('read failed'); + return snapshot; + } +} + +void main() { + test( + 'each cutoff finishes without waiting for later edits to become quiet', + () async { + final r = Rig(); + addTearDown(r.dispose); + r.repository + ..block(1) + ..block(2) + ..block(3); + final first = r.reconcile(); + await r.repository.snapshots[1]!.future; + r.repository.schedules = [r.schedule('second')]; + final second = r.reconcile(); + r.repository.releases[1]!.complete(); + expect((await first).armedScheduleIds, isEmpty); + await r.repository.snapshots[2]!.future; + r.repository.schedules = [r.schedule('third')]; + var thirdDone = false; + final third = r.reconcile().then((result) { + thirdDone = true; + return result; + }); + r.repository.releases[2]!.complete(); + expect((await second).armedScheduleIds, ['second']); + await r.repository.snapshots[3]!.future; + expect(thirdDone, false); + r.repository.releases[3]!.complete(); + expect((await third).armedScheduleIds, ['third']); + expect(r.registry.records.map((r) => r.scheduleId), ['third']); + }, + ); + + test( + 'failed snapshot completes its cutoff and still serves a newer request', + () async { + final r = Rig(); + addTearDown(r.dispose); + r.repository + ..block(1) + ..failedPasses.add(1); + final first = r.reconcile(); + await r.repository.snapshots[1]!.future; + r.repository.schedules = [r.schedule('new')]; + final second = r.reconcile(); + r.repository.releases[1]!.complete(); + expect((await first).status, AlarmReconciliationStatus.partial); + expect((await second).armedScheduleIds, ['new']); + expect(r.repository.alarmWindowRequestCount, 2); + }, + ); + + test( + 'failure alone stops; a request from completion starts another pass', + () async { + final r = Rig(); + addTearDown(r.dispose); + r.repository.failedPasses.add(1); + expect((await r.reconcile()).status, AlarmReconciliationStatus.partial); + await pumpEventQueue(); + expect(r.repository.alarmWindowRequestCount, 1); + r.repository.schedules = [r.schedule('later')]; + final result = await r.reconcile().then((_) => r.reconcile()); + expect(result.armedScheduleIds, ['later']); + expect(r.repository.alarmWindowRequestCount, 3); + }, + ); + + test( + 'replacement waits for the actual late native Future before same-ID reuse', + () async { + final native = BlockingNative(); + final r = Rig(native: native); + addTearDown(r.dispose); + r.repository.schedules = [r.schedule('same')]; + final first = r.reconcile(); + final invalidated = expectLater( + first, + throwsA(isA()), + ); + await native.entered.future; + var replaced = false; + final replacement = r.gate.run(() async { + await r.cancelAll.forDataReplacement(); + replaced = true; + r.repository.schedules = [r.schedule('same', hours: 3)]; + }, replacesData: true); + await invalidated; + await pumpEventQueue(); + expect(replaced, false); + expect(native.canceledNative, isEmpty); + await expectLater( + r.reconcile(), + throwsA(isA()), + ); + // No timeout, UI completion, or invalidated result releases the OS owner. + expect(r.registry.records.single.cancellationPending, true); + native.release.complete(); + await replacement; + expect(native.events, ['schedule:same', 'cancel:same']); + expect(native.pendingNative, isEmpty); + final newResult = await r.reconcile(); + expect(newResult.armedScheduleIds, ['same']); + expect(native.events, ['schedule:same', 'cancel:same', 'schedule:same']); + expect( + r.registry.records.single.alarmTime, + r.now.add(const Duration(hours: 3)), + ); + }, + ); + + test( + 'caller timeout cannot release ownership of an unreturned native Future', + () async { + final native = BlockingNative(); + final r = Rig(native: native); + addTearDown(r.dispose); + r.repository.schedules = [r.schedule('timeout')]; + final first = r.reconcile(); + await native.entered.future; + await expectLater( + first.timeout(Duration.zero), + throwsA(isA()), + ); + var replaced = false; + final replacement = r.gate.run(() async { + await r.cancelAll.forDataReplacement(); + replaced = true; + }, replacesData: true); + await pumpEventQueue(); + expect(replaced, false); + expect(native.events, isEmpty); + native.release.complete(); + await replacement; + expect(native.events, ['schedule:timeout', 'cancel:timeout']); + }, + ); + + test('ordinary export busy does not invalidate a requested pass', () async { + final r = Rig(); + addTearDown(r.dispose); + final picker = Completer(); + final export = r.gate.run(() => picker.future); + r.repository.schedules = [r.schedule('during-export')]; + expect((await r.reconcile()).armedScheduleIds, ['during-export']); + expect(r.gate.generation, 0); + picker.complete(); + await export; + }); + + test( + 'replacement cancellation failure retains evidence and prevents replacement', + () async { + final r = Rig(); + addTearDown(r.dispose); + r.repository.schedules = [r.schedule('old')]; + await r.reconcile(); + r.fallback.throwOnCancelIds.add('old'); + var replaced = false; + await expectLater( + r.gate.run(() async { + await r.cancelAll.forDataReplacement(); + replaced = true; + }, replacesData: true), + throwsA(isA()), + ); + expect(replaced, false); + expect(r.registry.records.single.cancellationPending, true); + expect( + r.gate.isAvailable, + true, + ); // Restore failure preserves usable data. + r.fallback.throwOnCancelIds.clear(); + expect((await r.reconcile()).armedScheduleIds, ['old']); + }, + ); + + test( + 'delete mutation accepted during snapshot cannot be resurrected by replaceAll', + () async { + final r = Rig(); + addTearDown(r.dispose); + r.repository.schedules = [r.schedule('deleted')]; + await r.reconcile(); + r.repository.block(2); + final old = r.reconcile(); + await r.repository.snapshots[2]!.future; + r.repository.schedules = []; // Durable deletion has committed. + final mutation = + ScheduleMutationAlarmEffectsCoordinator(r.cancelOne, r.reconcile)( + operation: ScheduleMutationAlarmOperation.deleted, + scheduleId: 'deleted', + ); + r.repository.releases[2]!.complete(); + await old; + await mutation; + await r.operations.cleanup(() async {}); + expect(r.repository.alarmWindowRequestCount, 3); + expect(r.registry.records, isEmpty); + expect(r.fallback.pendingFallback, isEmpty); + }, + ); + + test( + 'registry write failure is not armed and a queued newer request recovers ownership', + () async { + final registry = FailingRegistry(); + final r = Rig(registry: registry); + addTearDown(r.dispose); + r.repository + ..schedules = [r.schedule('latest')] + ..block(1); + final first = r.reconcile(); + final failure = expectLater(first, throwsStateError); + await r.repository.snapshots[1]!.future; + r.repository.schedules = [r.schedule('latest', hours: 2)]; + final latest = r.reconcile(); + r.repository.releases[1]!.complete(); + await failure; + expect((await latest).armedScheduleIds, ['latest']); + expect(registry.failedWrites, 1); + expect( + registry.records.single.alarmTime, + r.now.add(const Duration(hours: 2)), + ); + expect(r.fallback.canceledFallback.map((r) => r.scheduleId), ['latest']); + }, + ); + + test( + 'detailed OFF accepted after snapshot replaces old content in trailing pass', + () async { + final r = Rig(); + addTearDown(r.dispose); + r.repository + ..settings = const AlarmSettings( + alarmsEnabled: true, + detailedNotificationContent: true, + ) + ..schedules = [r.schedule('private')] + ..block(1); + final first = r.reconcile(); + await r.repository.snapshots[1]!.future; + r.repository.settings = const AlarmSettings( + alarmsEnabled: true, + detailedNotificationContent: false, + ); + final latest = r.reconcile(); + r.repository.releases[1]!.complete(); + await first; + expect((await latest).armedScheduleIds, ['private']); + expect(r.registry.records.single.deliveryContent.detailed, false); + expect( + r.registry.records.single.deliveryContent.title, + isNot(contains('private')), + ); + expect(r.fallback.canceledFallback.map((r) => r.scheduleId), ['private']); + }, + ); + + test( + 'request after schedule snapshot receives its own latest pass', + () async { + final now = DateTime.utc(2026, 9, 24); + final repository = BlockingAlarmRepository()..block(1); + final registry = fixtures.FakeAlarmRegistryRepository(); + final scheduler = fixtures.FakeAlarmSchedulerService(); + final fallback = fixtures.FakeFallbackAlarmNotificationService() + ..permission = AlarmPermissionState.granted; + final reconcile = ReconcileAlarmsUseCase.test( + repository, + registry, + scheduler, + fallback, + nowProvider: () => now, + timeZoneProvider: () async => 'UTC', + ); + final first = reconcile(); + await repository.snapshots[1]!.future; + repository.schedules = [ + fixtures.scheduleWithAlarmAt( + id: 'committed-after-snapshot', + alarmTime: now.add(const Duration(hours: 1)), + ), + ]; + final second = reconcile(); + repository.releases[1]!.complete(); + await first; + final result = await second; + + expect(result.armedScheduleIds, ['committed-after-snapshot']); + expect(registry.records.map((record) => record.scheduleId), [ + 'committed-after-snapshot', + ]); + expect(result.status, AlarmReconciliationStatus.armed); + }, + ); +} + +class BlockingNative extends fixtures.FakeAlarmSchedulerService { + BlockingNative() { + capabilities = const AlarmSchedulerCapabilities( + supportsNativeAlarm: true, + nativeAlarmProvider: AlarmProvider.iosAlarmKit, + fallbackProvider: AlarmProvider.localNotification, + ); + } + final entered = Completer(); + final release = Completer(); + final events = []; + @override + Future scheduleNativeAlarm(ScheduledAlarmRecord record) async { + if (!entered.isCompleted) { + entered.complete(); + await release.future; + } + await super.scheduleNativeAlarm(record); + events.add('schedule:${record.scheduleId}'); + } + + @override + Future cancelNativeAlarm(ScheduledAlarmRecord record) async { + await super.cancelNativeAlarm(record); + events.add('cancel:${record.scheduleId}'); + } +} + +class Rig { + Rig({ + fixtures.FakeAlarmSchedulerService? native, + fixtures.FakeAlarmRegistryRepository? registry, + }) { + this.registry = registry ?? fixtures.FakeAlarmRegistryRepository(); + scheduler = native ?? fixtures.FakeAlarmSchedulerService(); + operations = AlarmOperationCoordinator(gate); + reconcile = ReconcileAlarmsUseCase.test( + repository, + this.registry, + scheduler, + fallback, + operations: operations, + nowProvider: () => now, + timeZoneProvider: () async => 'UTC', + ); + cancelAll = CancelAllAlarmsUseCase( + this.registry, + scheduler, + fallback, + operations: operations, + ); + cancelOne = CancelScheduleAlarmUseCase( + this.registry, + scheduler, + fallback, + operations: operations, + ); + } + final now = DateTime.utc(2026, 9, 24); + final gate = LocalDataOperationGate(); + final repository = BlockingAlarmRepository(); + late final fixtures.FakeAlarmRegistryRepository registry; + final fallback = fixtures.FakeFallbackAlarmNotificationService() + ..permission = AlarmPermissionState.granted; + late final fixtures.FakeAlarmSchedulerService scheduler; + late final AlarmOperationCoordinator operations; + late final ReconcileAlarmsUseCase reconcile; + late final CancelAllAlarmsUseCase cancelAll; + late final CancelScheduleAlarmUseCase cancelOne; + ScheduleWithPreparationEntity schedule(String id, {int hours = 1}) => + fixtures.scheduleWithAlarmAt( + id: id, + alarmTime: now.add(Duration(hours: hours)), + ); + void dispose() { + operations.dispose(); + gate.dispose(); + } +} + +class FailingRegistry extends fixtures.FakeAlarmRegistryRepository { + int failedWrites = 0; + @override + Future replaceAll(List records) async { + if (failedWrites == 0 && + records.any((record) => !record.cancellationPending)) { + failedWrites++; + throw StateError('synthetic registry write failure'); + } + await super.replaceAll(records); + } +} diff --git a/test/domain/use-cases/cancel_alarms_use_cases_test.dart b/test/domain/use-cases/cancel_alarms_use_cases_test.dart index ece6cd2b..83b0ca18 100644 --- a/test/domain/use-cases/cancel_alarms_use_cases_test.dart +++ b/test/domain/use-cases/cancel_alarms_use_cases_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; import 'package:on_time_front/core/services/fallback_alarm_notification_service.dart'; @@ -28,12 +29,12 @@ void main() { expect(fallback.canceledFallback.map((record) => record.scheduleId), [ 'schedule-1', ]); - expect(registry.deletedScheduleIds, ['schedule-1']); + expect(registry.records.map((r) => r.scheduleId), ['schedule-2']); }, ); test( - 'CancelScheduleAlarmUseCase still deletes registry when platform cancel fails', + 'CancelScheduleAlarmUseCase preserves retry ownership when cancellation fails', () async { final registry = _FakeAlarmRegistryRepository([ _record('schedule-1', AlarmProvider.androidAlarmManager), @@ -45,9 +46,13 @@ void main() { _FakeFallbackAlarmNotificationService(), ); - await useCase('schedule-1'); + await expectLater( + useCase('schedule-1'), + throwsA(isA()), + ); - expect(registry.deletedScheduleIds, ['schedule-1']); + expect(registry.records.single.scheduleId, 'schedule-1'); + expect(registry.records.single.cancellationPending, isTrue); }, ); @@ -71,7 +76,7 @@ void main() { expect(fallback.canceledFallback.map((record) => record.scheduleId), [ 'fallback', ]); - expect(registry.deleteAllCount, 1); + expect(registry.records, isEmpty); }, ); @@ -85,7 +90,7 @@ void main() { await useCase(); - expect(registry.deleteAllCount, 1); + expect(registry.records, isEmpty); }); } @@ -104,7 +109,7 @@ ScheduledAlarmRecord _record(String scheduleId, AlarmProvider provider) { class _FakeAlarmRegistryRepository implements AlarmRegistryRepository { _FakeAlarmRegistryRepository(this.records); - final List records; + List records; final deletedScheduleIds = []; int deleteAllCount = 0; @@ -121,6 +126,11 @@ class _FakeAlarmRegistryRepository implements AlarmRegistryRepository { deleteAllCount += 1; } + @override + Future replaceAll(List next) async { + records = List.of(next); + } + @override noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } diff --git a/test/domain/use-cases/reconcile_alarms_use_case_test.dart b/test/domain/use-cases/reconcile_alarms_use_case_test.dart index b3d6c33a..3a7b9ee4 100644 --- a/test/domain/use-cases/reconcile_alarms_use_case_test.dart +++ b/test/domain/use-cases/reconcile_alarms_use_case_test.dart @@ -649,13 +649,9 @@ void main() { expect(result.status, AlarmReconciliationStatus.partial); expect(result.armedScheduleIds, isEmpty); expect(result.failures.single.scheduleId, 'private-failure'); - expect( - registryRepository - .records - .single - .payload['detailedNotificationContent'], - 'true', - ); + expect(registryRepository.records.single.payload, isEmpty); + expect(registryRepository.records.single.scheduleTitle, 'OnTime'); + expect(registryRepository.records.single.cancellationPending, isTrue); expect(fallbackService.scheduledFallback, hasLength(1)); expect(alarmRepository.settings.detailedNotificationContent, isFalse); }, @@ -1002,7 +998,7 @@ void main() { }, ); - test('coalesces overlapping reconciliation requests', () async { + test('overlapping requests each observe their requested pass', () async { alarmRepository.schedules = [ scheduleWithAlarmAt( id: 'eligible', @@ -1012,8 +1008,8 @@ void main() { final results = await Future.wait([useCase(), useCase()]); - expect(results[0], results[1]); - expect(alarmRepository.alarmWindowRequestCount, 1); + expect(results[0].armedScheduleIds, ['eligible']); + expect(results[1].armedScheduleIds, ['eligible']); expect(schedulerService.scheduledNative, isEmpty); expect(fallbackService.scheduledFallback.length, 1); }); diff --git a/test/domain/use-cases/recurring_alarm_integration_test.dart b/test/domain/use-cases/recurring_alarm_integration_test.dart index 2e5d1ecf..d2332d81 100644 --- a/test/domain/use-cases/recurring_alarm_integration_test.dart +++ b/test/domain/use-cases/recurring_alarm_integration_test.dart @@ -1,3 +1,15 @@ +import 'dart:async'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/domain/entities/schedule_with_preparation_entity.dart'; +import 'package:on_time_front/domain/repositories/alarm_repository.dart'; +import 'package:on_time_front/domain/use-cases/cancel_schedule_alarm_use_case.dart'; +import 'package:on_time_front/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart'; +import 'package:on_time_front/domain/use-cases/create_schedule_with_place_use_case.dart'; +import 'package:on_time_front/domain/use-cases/update_schedule_use_case.dart'; +import 'package:on_time_front/domain/use-cases/update_default_preparation_use_case.dart'; +import 'package:on_time_front/domain/use-cases/create_custom_preparation_use_case.dart'; +import 'package:on_time_front/domain/use-cases/update_preparation_by_schedule_id_use_case.dart'; import 'package:on_time_front/domain/entities/delivery_observation.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -126,6 +138,155 @@ void main() { await database.close(); }); + test( + 'actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop', + () async { + await createSeries('morning', DateTime.utc(2030, 1, 2, 9)); + final gate = LocalDataOperationGate(); + final owner = AlarmOperationCoordinator(gate); + addTearDown(owner.dispose); + final blocked = _BlockingAlarmWindow(alarms)..block(1); + final notifications = _Notifications(); + final useCase = ReconcileAlarmsUseCase.test( + blocked, + registry, + native, + notifications, + operations: owner, + nowProvider: () => now, + timeZoneProvider: () async => 'UTC', + ); + final effects = ScheduleMutationAlarmEffectsCoordinator( + CancelScheduleAlarmUseCase( + registry, + native, + notifications, + operations: owner, + ), + useCase, + ); + final first = useCase(); + await blocked.entered[1]!.future; + final early = ScheduleEntity( + id: 'new-nearest', + place: const PlaceEntity(id: 'new-place', placeName: 'New'), + scheduleName: 'New', + scheduleTime: now.add(const Duration(hours: 4)), + timeZoneId: 'UTC', + occurrenceOffsetSeconds: 0, + moveTime: Duration.zero, + isChanged: false, + isStarted: false, + scheduleSpareTime: Duration.zero, + scheduleNote: '', + ); + await CreateScheduleWithPlaceUseCase(schedules, effects)(early); + blocked.release[1]!.complete(); + await first; + await owner.cleanup(() async {}); + expect( + (await registry.loadAll()).map((record) => record.scheduleId), + contains('new-nearest'), + ); + expect(await registry.loadAll(), hasLength(60)); + await pumpEventQueue(); + expect(blocked.calls, 2); + + blocked.block(3); + final beforeUpdate = useCase(); + await blocked.entered[3]!.future; + await UpdateScheduleUseCase(schedules, effects)( + early.copyWith(scheduleTime: now.add(const Duration(days: 100))), + ); + blocked.release[3]!.complete(); + await beforeUpdate; + await owner.cleanup(() async {}); + expect( + (await registry.loadAll()).map((record) => record.scheduleId), + isNot(contains('new-nearest')), + ); + expect(await registry.loadAll(), hasLength(60)); + await pumpEventQueue(); + expect(blocked.calls, 4); + }, + ); + + test( + 'actual default and custom preparation commits request latest delivery timing', + () async { + final gate = LocalDataOperationGate(); + final owner = AlarmOperationCoordinator(gate); + addTearDown(owner.dispose); + final blocked = _BlockingAlarmWindow(alarms); + final useCase = ReconcileAlarmsUseCase.test( + blocked, + registry, + native, + _Notifications(), + operations: owner, + nowProvider: () => now, + timeZoneProvider: () async => 'UTC', + ); + await schedules.createSchedule( + ScheduleEntity( + id: 'prepared', + place: const PlaceEntity(id: 'p', placeName: 'Place'), + scheduleName: 'Prepared', + scheduleTime: now.add(const Duration(hours: 5)), + timeZoneId: 'UTC', + occurrenceOffsetSeconds: 0, + moveTime: Duration.zero, + isChanged: false, + isStarted: false, + scheduleSpareTime: Duration.zero, + scheduleNote: '', + ), + ); + await useCase(); + var previous = (await registry.loadAll()).single.alarmTime; + final mutations = <(Future Function(), int)>[ + ( + () => UpdateDefaultPreparationUseCase(preparations, useCase)( + _preparation(30), + ), + 60, + ), + ( + () => CreateCustomPreparationUseCase(preparations, useCase)( + _preparation(60), + 'prepared', + ), + -30, + ), + ( + () => UpdatePreparationByScheduleIdUseCase(preparations, useCase)( + _preparation(10), + 'prepared', + ), + 50, + ), + ]; + for (final (mutate, deltaMinutes) in mutations) { + final pass = blocked.calls + 1; + blocked.block(pass); + final old = useCase(); + await blocked.entered[pass]!.future; + await mutate(); + blocked.release[pass]!.complete(); + await old; + // Wait for the use case's accepted pass; do not inject a test request + // that could hide a missing post-commit integration. + await owner.cleanup(() async {}); + expect((await registry.loadAll()).map((record) => record.scheduleId), [ + 'prepared', + ]); + final actual = (await registry.loadAll()).single.alarmTime; + expect(actual, previous.add(Duration(minutes: deltaMinutes))); + previous = actual; + } + }, + ); + test( 'two unbounded series share the nearest 60 alarms and survive reopening', () async { @@ -277,3 +438,32 @@ class _Notifications extends Fake implements FallbackAlarmNotificationService { Future checkPermission() async => AlarmPermissionState.denied; } + +class _BlockingAlarmWindow implements AlarmRepository { + _BlockingAlarmWindow(this.delegate); + final AlarmRepository delegate; + int calls = 0; + final entered = >{}; + final release = >{}; + void block(int pass) { + entered[pass] = Completer(); + release[pass] = Completer(); + } + + @override + Future getAlarmSettings() => delegate.getAlarmSettings(); + @override + Future updateAlarmSettings({required bool alarmsEnabled}) => + delegate.updateAlarmSettings(alarmsEnabled: alarmsEnabled); + @override + Future> getAlarmWindow( + DateTime start, + DateTime end, + ) async { + final pass = ++calls; + final result = await delegate.getAlarmWindow(start, end); + entered[pass]?.complete(); + await release[pass]?.future; + return result; + } +} diff --git a/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart b/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart index 78f7caa6..fe0178b9 100644 --- a/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart +++ b/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart @@ -31,7 +31,7 @@ void main() { ); test( - 'deleted and finished schedules cancel targeted alarm before reconciling', + 'deleted and finished schedules accept reconciliation before awaiting cancellation', () async { final events = []; final cancel = _FakeCancelScheduleAlarmUseCase(events: events); @@ -52,10 +52,10 @@ void main() { await pumpEventQueue(); expect(events, [ + 'reconcile', 'cancel:schedule-1', 'reconcile', 'cancel:schedule-2', - 'reconcile', ]); }, ); diff --git a/test/domain/use-cases/schedule_preparation_session_use_case_test.dart b/test/domain/use-cases/schedule_preparation_session_use_case_test.dart index 50becf98..4b7449a6 100644 --- a/test/domain/use-cases/schedule_preparation_session_use_case_test.dart +++ b/test/domain/use-cases/schedule_preparation_session_use_case_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:rxdart/rxdart.dart'; import 'package:on_time_front/domain/entities/schedule_not_found.dart'; import 'dart:async'; @@ -57,6 +58,52 @@ void main() { }, ); + test( + 'cancellation failure does not undo committed start or skip its snapshot', + () async { + final schedules = _FakeScheduleRepository(); + final snapshots = _FakeTimedPreparationRepository(); + final early = _FakeEarlyStartSessionRepository(); + final reconcile = _FakeReconcileAlarmsUseCase(); + final useCase = SchedulePreparationSessionUseCase( + schedules, + _FakePreparationRepository(), + snapshots, + early, + _FakeCancelScheduleAlarmUseCase()..fails = true, + reconcile, + ); + final schedule = _scheduleWithPreparation('schedule-1'); + await useCase.startEarlySession(schedule, startedAt: DateTime.utc(2026)); + expect(schedules.startedScheduleIds, [schedule.id]); + expect(snapshots.snapshots[schedule.id], isNotNull); + expect(reconcile.callCount, 1); + }, + ); + + test( + 'cancellation failure after finish still clears completed runtime', + () async { + final schedules = _FakeScheduleRepository(); + final snapshots = _FakeTimedPreparationRepository(); + final early = _FakeEarlyStartSessionRepository(); + final reconcile = _FakeReconcileAlarmsUseCase(); + final useCase = SchedulePreparationSessionUseCase( + schedules, + _FakePreparationRepository(), + snapshots, + early, + _FakeCancelScheduleAlarmUseCase()..fails = true, + reconcile, + ); + await useCase.finishSchedulePreparation('schedule-1', latenessTime: 7); + expect(schedules.finishedSchedules, [('schedule-1', 7)]); + expect(snapshots.clearedScheduleIds, ['schedule-1']); + expect(early.clearedScheduleIds, ['schedule-1']); + expect(reconcile.callCount, 1); + }, + ); + test( 'matching timed preparation snapshot restores elapsed session progress', () async { @@ -498,10 +545,12 @@ class _FakeEarlyStartSessionRepository implements EarlyStartSessionRepository { class _FakeCancelScheduleAlarmUseCase implements CancelScheduleAlarmUseCase { final cancelledScheduleIds = []; + bool fails = false; @override Future call(String scheduleId) async { cancelledScheduleIds.add(scheduleId); + if (fails) throw const AlarmCleanupIncomplete(); } } diff --git a/test/domain/use-cases/timed_preparation_use_cases_test.dart b/test/domain/use-cases/timed_preparation_use_cases_test.dart index 55efd662..4ed75b66 100644 --- a/test/domain/use-cases/timed_preparation_use_cases_test.dart +++ b/test/domain/use-cases/timed_preparation_use_cases_test.dart @@ -1,3 +1,4 @@ +import '../../helpers/noop_alarm_reconciliation.dart'; import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; @@ -195,15 +196,18 @@ void main() { final repository = _FakePreparationRepository(); final preparation = _preparation('prep-1'); - await CreateCustomPreparationUseCase(repository)( - preparation, - 'schedule-1', - ); - await UpdateDefaultPreparationUseCase(repository)(preparation); - await UpdatePreparationByScheduleIdUseCase(repository)( - preparation, - 'schedule-2', - ); + await CreateCustomPreparationUseCase( + repository, + NoopAlarmReconciliation(), + )(preparation, 'schedule-1'); + await UpdateDefaultPreparationUseCase( + repository, + NoopAlarmReconciliation(), + )(preparation); + await UpdatePreparationByScheduleIdUseCase( + repository, + NoopAlarmReconciliation(), + )(preparation, 'schedule-2'); await UpdateSpareTimeUseCase(repository)(const Duration(minutes: 20)); expect(repository.customPreparationCalls, [(preparation, 'schedule-1')]); diff --git a/test/helpers/noop_alarm_cleanup.dart b/test/helpers/noop_alarm_cleanup.dart new file mode 100644 index 00000000..17014c1a --- /dev/null +++ b/test/helpers/noop_alarm_cleanup.dart @@ -0,0 +1,10 @@ +import 'package:on_time_front/domain/use-cases/cancel_all_alarms_use_case.dart'; + +/// Backup-only fixtures have no platform registrations. Concurrency integration +/// uses the real cleanup use case and a stateful platform fake instead. +class NoopAlarmCleanup implements CancelAllAlarmsUseCase { + @override + Future call() async {} + @override + Future forDataReplacement() async {} +} diff --git a/test/helpers/noop_alarm_reconciliation.dart b/test/helpers/noop_alarm_reconciliation.dart new file mode 100644 index 00000000..cb4c0130 --- /dev/null +++ b/test/helpers/noop_alarm_reconciliation.dart @@ -0,0 +1,23 @@ +import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; + +class NoopAlarmReconciliation implements ReconcileAlarmsUseCase { + int callCount = 0; + @override + Future call() async { + callCount++; + final now = DateTime.utc(2026); + return AlarmReconciliationResult( + status: AlarmReconciliationStatus.armed, + nativeAlarmProvider: AlarmProvider.none, + fallbackProvider: AlarmProvider.none, + armedScheduleIds: const [], + skippedScheduleCount: 0, + failures: const [], + scheduleWindowStart: now, + scheduleWindowEnd: now, + alarmCoverageStart: now, + alarmCoverageEnd: now, + ); + } +} diff --git a/test/presentation/my_page/my_data_restore_test.dart b/test/presentation/my_page/my_data_restore_test.dart index 53ed69b6..8ae15d12 100644 --- a/test/presentation/my_page/my_data_restore_test.dart +++ b/test/presentation/my_page/my_data_restore_test.dart @@ -1,3 +1,4 @@ +import '../../helpers/noop_alarm_cleanup.dart'; import 'package:drift/drift.dart' show Value; import 'package:drift/native.dart'; import 'package:flutter/material.dart'; @@ -61,6 +62,7 @@ void main() { final delegate = BackupService( database, _Metadata(), + NoopAlarmCleanup(), crypto: BackupCrypto(sodiumLoader: loadSodiumForTest), ); final candidate = await delegate.previewEncryptedBackup( diff --git a/test/presentation/my_page/my_page_screen_test.dart b/test/presentation/my_page/my_page_screen_test.dart index 472bb6d0..736f4360 100644 --- a/test/presentation/my_page/my_page_screen_test.dart +++ b/test/presentation/my_page/my_page_screen_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:on_time_front/domain/entities/delivery_observation.dart'; import 'package:on_time_front/core/database/local_data_operation_gate.dart'; import 'dart:async'; @@ -107,6 +108,40 @@ void main() { expect(reconcile.callCount, 2); }); + testWidgets( + 'committed detailed preference survives delivery invalidation without unhandled error', + (tester) async { + await _pumpMyPage(tester); + reconcile.fail = true; + final detail = find.widgetWithText(SwitchListTile, '알림에 일정 이름 표시'); + await tester.tap(detail); + await tester.pumpAndSettle(); + expect( + (await database.userDao.getAlarmSettings( + localProfileId, + )).detailedNotificationContent, + true, + ); + expect(tester.widget(detail).value, true); + expect(find.byType(SnackBar), findsOneWidget); + expect(tester.takeException(), isNull); + }, + ); + + testWidgets( + 'OFF cancellation failure keeps committed preference and shows cleanup status', + (tester) async { + await _pumpMyPage(tester); + cancelAll.fail = true; + reconcile.disabledStatus = AlarmReconciliationStatus.partial; + await tester.tap(find.byKey(const Key('alarmSettingsSwitch'))); + await tester.pumpAndSettle(); + expect(alarmRepository.settings.alarmsEnabled, false); + expect(find.text('Off · cancellation needs checking'), findsOneWidget); + expect(tester.takeException(), isNull); + }, + ); + testWidgets('disabling schedule delivery cancels every local registration', ( tester, ) async { @@ -117,7 +152,6 @@ void main() { expect(alarmRepository.updatedSettings, [false]); expect(cancelAll.callCount, 1); - expect(reconcile.callCount, 2); expect(find.text('꺼짐'), findsOneWidget); }); @@ -568,10 +602,12 @@ class _FakeReconcileAlarmsUseCase extends ReconcileAlarmsUseCase { int callCount = 0; List armedIds = []; AlarmReconciliationStatus status = AlarmReconciliationStatus.armed; + bool fail = false; @override Future call() async { callCount += 1; + if (fail) throw const AlarmOperationInvalidated(); return AlarmReconciliationResult( status: (await repository.getAlarmSettings()).alarmsEnabled ? status @@ -598,10 +634,12 @@ class _FakeCancelAllAlarmsUseCase extends CancelAllAlarmsUseCase { ) : super(registryRepository, schedulerService, fallbackNotificationService); int callCount = 0; + bool fail = false; @override Future call() async { callCount += 1; + if (fail) throw const AlarmCleanupIncomplete(); } } diff --git a/test/presentation/recurring/recurring_form_integration_test.dart b/test/presentation/recurring/recurring_form_integration_test.dart index 8cf27693..7f584092 100644 --- a/test/presentation/recurring/recurring_form_integration_test.dart +++ b/test/presentation/recurring/recurring_form_integration_test.dart @@ -1,3 +1,4 @@ +import '../../helpers/noop_alarm_reconciliation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:on_time_front/core/di/di_setup.dart'; @@ -141,12 +142,15 @@ void main() { ), CreateScheduleFormSubmissionUseCase( CreateScheduleWithPlaceUseCase(schedules, alarms), - CreateCustomPreparationUseCase(preparations), + CreateCustomPreparationUseCase(preparations, NoopAlarmReconciliation()), recurringSchedules: useCase, ), UpdateScheduleFormSubmissionUseCase( UpdateScheduleUseCase(schedules, alarms), - UpdatePreparationByScheduleIdUseCase(preparations), + UpdatePreparationByScheduleIdUseCase( + preparations, + NoopAlarmReconciliation(), + ), recurringSchedules: useCase, ), recurringSchedules: useCase, From 818e99c99f9a3892fb2588bd471d2ea21a00f42c Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 01:50:37 +0900 Subject: [PATCH 30/59] docs: record A15 delivery and start D03 recovery implementation --- handoff/audit-2026-09-23-progress.md | 12 ++++++------ plans/audit-2026-09-23/README.md | 4 ++-- plans/audit-2026-09-23/a15-validation.json | 2 +- plans/audit-2026-09-23/issues/A15.md | 4 ++++ plans/audit-2026-09-23/state.json | 6 +++--- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index e4c06d3b..4db6f535 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -14,7 +14,7 @@ - 감사 baseline44067d7a. main2b02fb77(PR590 recurring)을712b3708에서 통합했다. schema2/v1 migration, backupformat2/v1 호환, recurring ownership/frozen/exclusions, nearest60을 유지한다. - 상세 이슈 **21/64개** 게시·exact read-back 완료, **43개 생성 전**. - A01#583 A02#584 A03#586 A04#587 A05#588 A11#589 A12#591 A13#592 A14#593 A15#594 D03#595 U08#597 A06#598 A07#599 C01#600 A08#601 A09#602 D01#603 D02#604 D04#605 D05#606. -- A01–A05/A11/A12/A13 자동검증 통과, 기기/후속 통합 조건은 남음. U08 홈 overflow 선행 수정만 포함. A14 구현/CI 후 실제 registry ownership 보존 문제를 A15에서 후속 수정 중. A15는 아직 source freeze 전. +- A01–A05/A11/A12/A13 자동검증 통과, 기기/후속 통합 조건은 남음. U08 홈 overflow 선행 수정만 포함. A14 구현/CI 후 실제 registry ownership 보존 문제를 A15에서 후속 수정 중. A15는37파일 해시·795 tests/analyze 검증 뒤 ecd04293363fa79bd9cd1b140169619dfc083b96로 커밋했다. - D05까지 이슈별 설계가 확정됐으나 설계 게시와 구현 완료는 다르다. ## 최근 최종 검증 @@ -24,14 +24,14 @@ - A14 282db21f source24 해시·focused100/full772/analyze/iOS26.5 SDK typecheck/AndroidSDK36 Kotlin compile 통과. artifacts/a14에는 실제 MyPage widget KO/EN PNG와 로그가 있음. OS 실기기 화면 아님. - A14 b81259bf: Flutter35887794993 full772/analyze,87.44%(11487/13137), Android35887794956 통과. artifact10763053548/synthetic3ebcf192/APK7e88d688...; a14-ci-validation.json. - A15 전 bebf2a31: Flutter35889125749 full772/analyze,87.33%(11472/13137), Android35889125735 통과. artifact10764089340/synthetic e53967c1/APKf8d95a14...; pre-a15-ci-validation.json. 동일 제품 소스여도 각 run의 coverage와 artifact identity를 섞지 않는다. -- A15 테스트: 결정적 snapshot barrier가 수정 전 실패. actual BackupService+Drift+owner restore/reset 경계33, UI/privacy/CRUD focused35 등 중간 결과 통과. 실제 CRUD/preparation/recurring nearest60 통합 후 최종 full/analyze 실행 중이며 최종 통과 숫자는 아직 없음. +- A15 테스트: 결정적 snapshot barrier가 수정 전 실패. actual BackupService+Drift+owner restore/reset 경계33, UI/privacy/CRUD focused35 등 중간 결과 통과. 실제 CRUD/preparation/recurring nearest60 통합 후 최종 frozen source full795/analyze 통과, aggregate de887b4e5d34a0ac9c37d62e92a652128fabde3ae1ddcb6dc54d6eccff446dd2. a15-validation.json에37파일/로그해시·초기실패·수정 근거를 기록했다. ## 현재 에이전트 및 다음 작업 -1. `/root/grill_a15` 원래 전담이 source/test/A15.md/receipt를 소유해 구현 중. root는 tracking/GitHub/commit 소유. Flutter 명령을 병행하지 않는다. -2. A15는 단일 native owner·request cutoff/trailing drain·replacement generation을 구현한다. 취소 실패 최소 ownership을 실제 platform ID로 보존하고 repo upsert/dedup 왕복을 검사한다. reset marker 후 실패해도 DB/키를 지우거나 새 writer를 열지 않는다. DB start/finish 후 취소 오류를 durable commit 실패처럼 취급하지 않는다. actual BackupService/Reset/CRUD/Preparation/recurring 통합 증거 포함 필요. -3. A15 source freeze 이후 root hash/최종 tests/analyze/issue 전체 리뷰 반영을 확인하고 #594 read-back→명시적 staging→commit/push→새 SHA CI. A14 #593의 실제 ownership 후속 상태도 증거에 맞게 갱신한다. -4. D03 원래 전담 `/root/grill_d03` followup이 접수되어 읽기 전용 구현 준비 중이다. root가 A15 commit SHA를 전달하기 전 소스/테스트/문서 편집 및 Flutter 명령은 금지했다. 이후 durable crash journal/cleanup을 구현한다. A15 인메모리 큐가 D03의 재시작 복구까지 완료했다고 주장하지 않는다. 전체 구현 순서는 README를 따른다. +1. A15 원래 전담은 최종37파일 동결을 완료했고 root가 source/log hash를 대조해 ecd04293로 commit/push했다. #594/#593의 로컬 검증 본문을 exact read-back했다. 새 원격 CI를 해당 전체SHA로 추적한다. +2. A15에는 단일 native owner·request cutoff/trailing drain·replacement generation, 실제 platform ID ownership 보존, reset 실패 gate와 actual BackupService/Reset/CRUD/Preparation/recurring 회귀가 포함된다. 실제 mutation 테스트의 추가 reconcile 호출을 제거해 접수 누락을 숨기지 않는다. +3. `/root/grill_d03` 원래 전담에 ecd04293363fa79bd9cd1b140169619dfc083b96를 전달하고 D03 소스/테스트/D03문서/receipt 구현 권한을 줬다. root는 tracking/GitHub/commit 소유. Flutter 명령을 병행하지 않는다. +4. D03은 A15 owner/cleanup을 재사용해 independent durable journal, bootstrap interrupted-reset, typed partial/retry UI, 최소 D01 시작 복구를 구현한다. 영속 journal 검증 후 A15의 임시 '취소 실패면 DB 보존/같은프로세스 재시도 금지'를 확정 D03의 'quiesce된 취소반환 후 원문삭제+cleanup pending/retry' 계약으로 확장할 수 있다. 실제 사용자데이터/기존simulator는 보존한다. 5. D05 신규 전담 grill을 완료해 #606에 게시했다. 입력 암호문72MiB/평문64MiB/중첩32/일정100k/전체500k/정의별steps1000, 실제 bounded parsing·자료/그래프/시간대 검증·export 대칭·모바일 측정 계약이다. legacy duration에1440분을 일괄 강제하지 않고 과거 명시 offset 의미를 보존한다. D05 문답과 수용기준만 확정, 구현 전이다. 6. A10 사전 read-only 확인: ScheduleBloc:244/835, Home timer:39, TodaysScheduleTile:158, adjacent usecase:54/73에 scheduleTime 직접 비교가 남아 있다. occurrenceInstantUtc의 offset-null fallback은 device-local toUtc이고 CivilTimeResolver unknown zone은 UTC fallback이다. 이 사실만으로 모든 civil-date 조회를 instant 비교로 일괄 치환하지 말고 A10 전담이 calendar bucket/commitment 의미를 구분해야 한다. 다음 신규 전담은 A10. 이번 생성 시도는 agent thread limit으로 거절됐다. 다른 항목 전담을 재사용하지 말고 다음 실행에서 새 agent 생성. 사용자 승인 대기가 아니다. 그동안 A15 리뷰/검증을 계속한다. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 8292a26e..634ed0f1 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -26,7 +26,7 @@ | A13 | [#592](https://github.com/DevKor-github/OnTime-front/issues/592) | 예약/안내·registry 후속 수정 원격740 tests/87.20%·APK 통과, 기기 대기 | | A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측 구현282db21f, 원격772 tests/87.44%·APK 통과, A15 실제 ownership 후속 로컬795 통과·기기 대기 | | A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 요청별 후속 재조정/교체 직렬화 구현, 최종 로컬795 tests/analyze 통과·원격/기기 대기 | -| D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 실제4문답·취소 journal/중단 복구/부분 결과 기준 확정, A15 뒤 구현 | +| D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 실제4문답·취소 journal/중단 복구/부분 결과 기준 확정, A15 뒤 전담 구현 중 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | 실제3문답·정정 포함, 신선한 실제 타이머 전환 알림 기준 확정 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | 실제3문답·추가 정정 포함, DB 원자적 시작·run 시각·실패 재시도 기준 확정 | | C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 실제3문답·staging/gate 보충 포함, 구체 workflow·부분 결과·통합 의존성 확정 | @@ -38,7 +38,7 @@ | D05 | [#606](https://github.com/DevKor-github/OnTime-front/issues/606) | 실제4문답·bounded stream/자료 한도·시간대/그래프 검증 기준 확정 | | U08 | [#597](https://github.com/DevKor-github/OnTime-front/issues/597) | 홈 overflow 선행 수정18 tests/analyze 통과, 전체 매트릭스·기기 검증 미완료 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **21/64개**를 게시하고 원격 본문 동일성을 확인했으며 **43개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14 로컬772 tests·native SDK 검증 후282db21f로 커밋했고 A15 전담 구현을 시작했다. D05까지 실제 문답과 백업·복구 검증 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **21/64개**를 게시하고 원격 본문 동일성을 확인했으며 **43개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 새 원격 CI를 확인하며 D03 전담 구현을 시작했다. D05까지 실제 문답과 백업·복구 검증 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/a15-validation.json b/plans/audit-2026-09-23/a15-validation.json index 08042d70..10155eef 100644 --- a/plans/audit-2026-09-23/a15-validation.json +++ b/plans/audit-2026-09-23/a15-validation.json @@ -3,7 +3,7 @@ "issue_number": 594, "recorded_at": "2026-09-24T01:47:52.715324+09:00", "base_head": "d2ae69d3f0e139d2d88c46a7f1e0b8da5e403830", - "implementation_commit": null, + "implementation_commit": "ecd04293363fa79bd9cd1b140169619dfc083b96", "scope": "Dart source, real Drift/SharedPreferences persistence, actual use-case/service integrations, blocking fake OS boundaries; no actual OS delivery proof", "source_freeze": { "file_count": 37, diff --git a/plans/audit-2026-09-23/issues/A15.md b/plans/audit-2026-09-23/issues/A15.md index a796be7f..c0babd8b 100644 --- a/plans/audit-2026-09-23/issues/A15.md +++ b/plans/audit-2026-09-23/issues/A15.md @@ -235,3 +235,7 @@ upstream recurrence 규칙·DB schema2·backup v2를 유지한다. nearest eligi - registry와 메모리 모두 사라지는 프로세스 사망, interrupted reset bootstrap 전체, persistent crash journal은 D03 의존이다. 손상 DB manifest/복구 계약은 D02 의존이며 구현하지 않았다. - 한 pass 안의 여러 DB 읽기 transaction snapshot 원자성은 A08/C01 경계다. 새로운 commit 뒤 실제 접수된 요청이 다음 pass에 반영된다는 A15 보장을 검증했다. - 일반 설정 저장 경합의 전면 재설계는 U03 범위다. 이번 변경은 저장 성공을 알림 실패로 롤백하지 않고 관찰 가능한 실패를 남기는 최소 통합이다. + +## Root 전달 확인 + +구현 커밋은 `ecd04293363fa79bd9cd1b140169619dfc083b96`다. Root가37 source/test 파일 및 모든 raw/copied 로그의 SHA256을 독립 대조하고 최종795 tests/analyze 결과를 확인했다. 원격 CI는 push 이후 해당 HEAD 기준으로 별도 확인하며 아직 통과를 주장하지 않는다. 실제 기기 조건이 남아 이슈는 OPEN으로 유지한다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index d64bdc1c..68fd8342 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -328,7 +328,7 @@ "issue_number": 594, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/594", "decisions_file": "plans/audit-2026-09-23/issues/A15.md", - "implementation_commit": null, + "implementation_commit": "ecd04293363fa79bd9cd1b140169619dfc083b96", "pr_url": "https://github.com/DevKor-github/OnTime-front/pull/585", "verification": [ "Deterministic post-snapshot request regression failed before fix; trailing pass now covers each accepted cutoff", @@ -343,7 +343,7 @@ ], "sequence": 10, "readback_verified": true, - "body_sha256": "016d79f9ab44a022c92cbab9237e47515fe5e0f8a643b9501d471e46cde453d9" + "body_sha256": "4ce883db47e54c4605b2ede3d4cdccb1a0379e17778407452b7492c4409b2948" }, { "audit_id": "D03", @@ -356,7 +356,7 @@ "`local_data_reset_service.dart:19-25`; `local_data_lifecycle.dart:45-69`; `cancel_schedule_alarm_use_case.dart:25-37`; `cancel_all_alarms_use_case.dart:19-41`; `reconcile_alarms_use_case.dart:549-570`; 관련 테스트 `:951-975`", "이미 없음과 실제 취소 실패 구분, 실패 기록 보존·재시도·OS read-back. 중간 강제 종료 후 정리 완료. reset marker는 검증 가능한 종료 상태까지 유지." ], - "status": "ready", + "status": "implementing", "agent": "/root/grill_d03", "issue_number": 595, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/595", From 0efdd3adb28293d3ff9f6b80bcd9091229384bd5 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 09:38:19 +0900 Subject: [PATCH 31/59] docs: verify final A15 CI and preserve D03 recovery handoff --- handoff/audit-2026-09-23-progress.md | 18 ++++++++ plans/audit-2026-09-23/README.md | 4 +- plans/audit-2026-09-23/a15-ci-validation.json | 42 +++++++++++++++++++ plans/audit-2026-09-23/issues/A14.md | 2 + plans/audit-2026-09-23/issues/A15.md | 4 ++ plans/audit-2026-09-23/state.json | 16 +++---- 6 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 plans/audit-2026-09-23/a15-ci-validation.json diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 4db6f535..79a18a7a 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -50,3 +50,21 @@ D04#605: 현재v1→v2가 이미 있다는 사실로 baseline 정정. DDL/versio - 부팅된 iPhone17/iOS26.5 simulator OnTime Recurring QA 20260923 (C8BA159D-A97A-458B-A5FC-B04A8B6F2848)의 upstream QA 앱·데이터는 보존한다. 발견만으로 실제 전달·provider·양방향복원 검증을 통과 처리하지 않는다. - `/Users/ejunpark/.codex/worktrees/7b29/OnTime-front`는 다른 작업이다. 수정하지 않는다. - helper `/tmp/ontime-ci-audit.py`는 전체40자리 head SHA를 요구하며 해당 head의 Flutter와 Android 결과/작은 artifact identity를 기록한다. short SHA의 빈 결과를 근거로 쓰지 않는다. + +## 최신 push 및 진행 중 CI + +최신 pushed HEAD는818e99c99f9a3892fb2588bd471d2ea21a00f42c(A15 전달 추적)다. PR585 본문 exact read-back 및 Draft를 확인했다. Flutter run35891562919는 in_progress, Android35891562976은 pending, Widgetbook35891562910은 조건상 skipped다. 다음 실행은 같은 전체SHA의 최종 결과를 기록한다. source 구현 SHA는ecd04293363fa79bd9cd1b140169619dfc083b96다. D03 전담에 구현 권한이 전달됐으므로 source37 freeze 검증을 이후 D03 WIP와 혼동하지 않는다. + +## A15 최종 CI / D03 root 인계 / A10 사용량 제한 + +818e99c99f9a3892fb2588bd471d2ea21a00f42c의 Flutter35891562919 전체795/analyze·coverage87.66%(11668/13310), Android35891562976 APK/manifest 모두 성공. artifact10765597396/synthetic1d3f41c0/APK02285d398344cff0b2ae7ebeadc30456c7622715df488fa862eacc2db29ed1ff. a15-ci-validation.json과 #594/#593/PR585 exact read-back 완료. + +A10 전담 `/root/grill_a10` 신규 생성은 성공했지만 실제 질문 전에 usage limit 오류로 종료됐다. A10 이슈/문답을 만들었다고 주장하지 않는다. 같은 agent 재개 가능 시 신규 다른 전담을 쓰지 않고 이어간다. D03도 usage limit으로 종료돼 root가 이미 확정된4문답에 따라 이어받았다. 이는 사용자가 승인하지 않은 작업이거나 목표 전체 blocker라는 뜻이 아니다. + +D03 WIP: 신규 alarm_journal_store{,_native,_web}, alarm_ownership_journal, local_reset_protocol 및 기존 owner/cleanup/CancelAll 변경. root는 staged-first-write를 빈 설치로 취급하지 않게 하고, journal 중복 실제ID/불가능한 완료상태 거부, intent write 응답 불명확 시 데이터삭제 금지·gate보호, pending ownership의 기존 timing enum 보존을 추가했다. 실제 임시파일 23개 focused 테스트와 analyze 통과. d03-journal-progress.json에 해당 파일/로그 해시와 한계를 기록했다. 현재 제품 서비스/bootstrap/UI에는 아직 프로토콜 연결 전이며 전체 regression/실제kill/device는 미실행이다. D03 WIP를 A15의795 전체검증 결과와 섞지 않는다. + +D03 다음: actual LocalResetActions(검증 가능한 marker/key/prefs/file/launch삭제), LocalDataResetService+bootstrap 공통 프로토콜, D01 최소 pre-DI 복구 shell/KO·EN partial·retry UI 연결. runtime shared owner가 이제 actual FileStore이므로 기존 test fixture가 path_provider 없는 shared singleton에 의존하던 부분을 명시적 격리 주입으로 정비하되 product에서 MemoryStore로 조용히 우회하지 않는다. actual file tests는 반드시 유지. registry decode 실패를 빈 성공으로 바꾸지 말고 orphan/unknown provider 관측과 독립 journal로 안전하게 연결. staged-only first-write가 영구막힘으로 끝나지 않도록 명시적 보존/복구 경로가 필요하다. 무응답 Future는 owner를 유지하고 UI만 제한 대기로 표시해야 한다. + +공식 Dart3.12.2 runtime/bin/file_macos.cc는 rename(), file_linux.cc는 renameat() 호출을 확인했다. 같은 디렉터리 temp→rename로 이전 committed 파일을 유지하는 설계이며 directory fsync/갑작스런 전원손실 보장은 주장하지 않는다. Dart 공개 File.rename 문서만으로 원자성을 입증한 것이 아니다. Android backup_rules/data_extraction_rules의 root/file 제외는 존재하고 iOS 기존 excludeFromBackup bridge를 호출하지만 실제 mobile read-back/backup 검증은 남아 있다. + +현재 sandbox는 workspace-write/auto-review로 바뀌었다. Flutter SDK 캐시, GitHub 네트워크, git index/ref 변경은 필요한 require_escalated 실행으로 자동검토를 받았다. 거절은 없었고 승인된 읽기/테스트는 완료했다. 기본 권한 Flutter 실행은 SDK telemetry/cache 경로로 실패했으며 제품 테스트 실패가 아니다. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 634ed0f1..abab8bd8 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -25,7 +25,7 @@ | A12 | [#591](https://github.com/DevKor-github/OnTime-front/issues/591) | 콜드 탭·선택 준비 유지 구현, 최종 b9f959db 원격707 tests/analyze·coverage87.11%·APK 통과, 기기 검증 대기 | | A13 | [#592](https://github.com/DevKor-github/OnTime-front/issues/592) | 예약/안내·registry 후속 수정 원격740 tests/87.20%·APK 통과, 기기 대기 | | A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측 구현282db21f, 원격772 tests/87.44%·APK 통과, A15 실제 ownership 후속 로컬795 통과·기기 대기 | -| A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 요청별 후속 재조정/교체 직렬화 구현, 최종 로컬795 tests/analyze 통과·원격/기기 대기 | +| A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 요청별 후속 재조정/교체 직렬화 구현, 최종 원격795 tests/analyze·87.66%·APK 통과, 기기 대기 | | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 실제4문답·취소 journal/중단 복구/부분 결과 기준 확정, A15 뒤 전담 구현 중 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | 실제3문답·정정 포함, 신선한 실제 타이머 전환 알림 기준 확정 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | 실제3문답·추가 정정 포함, DB 원자적 시작·run 시각·실패 재시도 기준 확정 | @@ -38,7 +38,7 @@ | D05 | [#606](https://github.com/DevKor-github/OnTime-front/issues/606) | 실제4문답·bounded stream/자료 한도·시간대/그래프 검증 기준 확정 | | U08 | [#597](https://github.com/DevKor-github/OnTime-front/issues/597) | 홈 overflow 선행 수정18 tests/analyze 통과, 전체 매트릭스·기기 검증 미완료 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **21/64개**를 게시하고 원격 본문 동일성을 확인했으며 **43개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 새 원격 CI를 확인하며 D03 전담 구현을 시작했다. D05까지 실제 문답과 백업·복구 검증 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **21/64개**를 게시하고 원격 본문 동일성을 확인했으며 **43개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05까지 실제 문답과 백업·복구 검증 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/a15-ci-validation.json b/plans/audit-2026-09-23/a15-ci-validation.json new file mode 100644 index 00000000..734a6c2e --- /dev/null +++ b/plans/audit-2026-09-23/a15-ci-validation.json @@ -0,0 +1,42 @@ +{ + "head_commit": "818e99c99f9a3892fb2588bd471d2ea21a00f42c", + "runs": [ + { + "conclusion": "success", + "databaseId": 35891562976, + "event": "pull_request", + "headSha": "818e99c99f9a3892fb2588bd471d2ea21a00f42c", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35891562976", + "workflowName": "Android Notification Contract", + "artifact_id": 10765597396, + "identity": { + "commit": "1d3f41c0e7e721d86deaf4fd9d7aeeecaf2399b3", + "artifact": "build/app/outputs/flutter-apk/app-release.apk", + "sha256": "02285d398344cff0b2ae7ebeadc30456c7622715df488fa862eacc2db29ed1ff", + "signing": "disposable CI validation key; not a store release", + "merged_manifests": [ + "build/app/intermediates/merged_manifest/release/outputReleaseAppLinkSettings/AndroidManifest.xml", + "build/app/intermediates/merged_manifest/release/processReleaseMainManifest/AndroidManifest.xml", + "build/app/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml" + ], + "device_delivery_verified": false + } + }, + { + "conclusion": "success", + "databaseId": 35891562919, + "event": "pull_request", + "headSha": "818e99c99f9a3892fb2588bd471d2ea21a00f42c", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35891562919", + "workflowName": "Flutter Testing", + "tests_passed": 795, + "coverage": { + "percent": 87.66, + "lines_hit": 11668, + "lines_found": 13310 + } + } + ] +} diff --git a/plans/audit-2026-09-23/issues/A14.md b/plans/audit-2026-09-23/issues/A14.md index 6ae97f5f..f598379f 100644 --- a/plans/audit-2026-09-23/issues/A14.md +++ b/plans/audit-2026-09-23/issues/A14.md @@ -239,3 +239,5 @@ A13 커밋 `59d92bdd` 이후 구현했으며, 현재 기반 HEAD는 `e2fbdf0bb23 head `b81259bf66881289bf6bca9ac7cca2548de6a326`의 [Flutter run35887794993](https://github.com/DevKor-github/OnTime-front/actions/runs/35887794993)은 전체772 tests/analyze 및 coverage87.44%(11487/13137)를 통과했다. [Android run35887794956](https://github.com/DevKor-github/OnTime-front/actions/runs/35887794956)도 release APK/manifest를 통과했다. artifact10763053548, synthetic merge3ebcf19237dcdd87e262c176762149dfd7ef0e8b, APK SHA2567e88d6882e3d1060965b33e7d6bab12e04f5aff3a4be5448e1ec859f8c314b60을 read-back했다. receipt는 a14-ci-validation.json이다. 이후 A15 통합 리뷰에서 실제 AlarmRegistryRepositoryImpl.replaceAll이 scheduleId로 dedup하는 경계를 확인했다. 같은 일정에 서로 다른 provider/실제 platform ID의 취소 ownership이 여러 개 있으면 하나를 잃을 수 있다. 기존 fake registry 중심 회귀와772개 통과가 이 실제 persistence 문제를 검증한 것은 아니다. A15 #594의 후속 구현은 플랫폼 소유 ID 기준 보존, 동일 일정의 keeper 외 취소·실패 보존, 실제 repo/data source와 SharedPreferences mock backend 왕복 회귀를 포함한다. Root가 최종37 source/test 파일의 검증 전후·현재 SHA256 일치 및 전체795 tests/analyze 통과를 확인했다. cancellationPending 최소 record에서도 실제 notificationTiming enum을 보존한다. 이 후속 로컬 검증은 실제 기기 디스크/OS 전달 증거가 아니며 새 커밋 원격 CI를 별도로 확인한다. 실기기 조건이 남아 A14 이슈는 OPEN으로 유지한다. + +A15 후속 최종 head818e99c9의 원격795 tests/analyze·coverage87.66% 및 Android APK/manifest도 통과했다. 실제 persistence ownership 보존 후속은 이 검증에 포함됐으며 receipt는 a15-ci-validation.json이다. 실기기 조건은 계속 미완료다. diff --git a/plans/audit-2026-09-23/issues/A15.md b/plans/audit-2026-09-23/issues/A15.md index c0babd8b..495b9f2c 100644 --- a/plans/audit-2026-09-23/issues/A15.md +++ b/plans/audit-2026-09-23/issues/A15.md @@ -239,3 +239,7 @@ upstream recurrence 규칙·DB schema2·backup v2를 유지한다. nearest eligi ## Root 전달 확인 구현 커밋은 `ecd04293363fa79bd9cd1b140169619dfc083b96`다. Root가37 source/test 파일 및 모든 raw/copied 로그의 SHA256을 독립 대조하고 최종795 tests/analyze 결과를 확인했다. 원격 CI는 push 이후 해당 HEAD 기준으로 별도 확인하며 아직 통과를 주장하지 않는다. 실제 기기 조건이 남아 이슈는 OPEN으로 유지한다. + +## 최종 원격 CI + +최종 전달 head `818e99c99f9a3892fb2588bd471d2ea21a00f42c`의 [Flutter run35891562919](https://github.com/DevKor-github/OnTime-front/actions/runs/35891562919)은 전체795 tests/analyze 및 coverage87.66%(11668/13310)를 통과했다. [Android run35891562976](https://github.com/DevKor-github/OnTime-front/actions/runs/35891562976)도 release APK/manifest 검증을 통과했다. artifact10765597396, synthetic merge1d3f41c0e7e721d86deaf4fd9d7aeeecaf2399b3, APK SHA25602285d398344cff0b2ae7ebeadc30456c7622715df488fa862eacc2db29ed1ff을 read-back했다. 상세 receipt는 a15-ci-validation.json이다. 실제 OS 전달과 D03 중단 복구 완료를 의미하지 않는다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 68fd8342..cbee7629 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -301,16 +301,16 @@ "Frozen source24 SHA256 matched; focused100/full772, analyzer, native Swift SDK/Kotlin compile, generated/local-only checks passed. a14-validation.json.", "Root visually reviewed actual MyPage standalone KO/EN captures; fake receipts/in-memory DB, not physical OS screenshots.", "Remote b81259bf: Flutter772/analyze, coverage87.44% (11487/13137), Android APK/manifest passed. a14-ci-validation.json.", - "A15 followup local795/analyze and actual registry/data source ownership roundtrip passed; final remote CI separately pending" + "A15 followup local795/analyze and actual registry/data source ownership roundtrip passed; final remote CI separately pending", + "A15 final818e99c9: remote795/analyze, coverage87.66%(11668/13310), Android APK/manifest passed; a15-ci-validation.json" ], "blockers": [ - "Actual Android/iOS delivery and permission/Doze/reboot matrix not executed.", - "A15 ownership followup local verification passed; final remote CI pending." + "Actual Android/iOS delivery and permission/Doze/reboot matrix not executed." ], "sequence": 9, "issue_body_sha256": "23769c09503d629f813df5b93db7b2b6fce715dd15ea1571ea09fa06b9c1a5d4", "readback_verified": true, - "body_sha256": "5efdc7fd7cc9debee0f5f7fed3f5146d346eb8ca52f9286b41960f750304b888" + "body_sha256": "61cd6d895147c01b682e9871307d355c4b2bf5847627f6f72a00c6909d97d495" }, { "audit_id": "A15", @@ -335,15 +335,17 @@ "Final frozen source: 795 Flutter tests and analyzer passed; root independently compared 37 before/after/current source hashes", "Actual BackupService/Reset + Drift integration preserves replacement ordering and pending reset safety; fake OS and mock secure storage are not device evidence", "Actual CRUD/preparation/recurring usecases trigger trailing reconciliation without test-injected extra calls; nearest60 and no self-dirty loop checked", - "Actual registry repository/data source and SharedPreferences mock backend retain multiple platform ownerships and cancellation timing enum" + "Actual registry repository/data source and SharedPreferences mock backend retain multiple platform ownerships and cancellation timing enum", + "A15 final818e99c9: remote795/analyze, coverage87.66%(11668/13310), Android APK/manifest passed; a15-ci-validation.json" ], "blockers": [ "Real Android/iOS concurrent edits, restore/reset and delivery verification pending", - "Remote CI for final A15 commit pending; D03 durable crash journal and A09 full restore runtime cleanup remain separate" + "Remote CI for final A15 commit pending; D03 durable crash journal and A09 full restore runtime cleanup remain separate", + "D03 durable crash journal and A09 full restore runtime cleanup remain separate follow-ups" ], "sequence": 10, "readback_verified": true, - "body_sha256": "4ce883db47e54c4605b2ede3d4cdccb1a0379e17778407452b7492c4409b2948" + "body_sha256": "ea30e4068c598fe9632e8a56e08a5bffc8eb661079e8eb2d74c6ed5acefba569" }, { "audit_id": "D03", From e903db9eb5b4258f1d27512526d1b08c94a3c031 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 10:23:18 +0900 Subject: [PATCH 32/59] docs: specify zoned schedule consistency for audit A10 --- CONTEXT.md | 2 +- ...1-anchor-schedules-to-a-named-time-zone.md | 15 +- plans/audit-2026-09-23/issues/A10.md | 199 ++++++++++++++++++ 3 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/A10.md diff --git a/CONTEXT.md b/CONTEXT.md index ca4114af..d51aedba 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -408,7 +408,7 @@ _Avoid_: Loaded range, stream range, cached range - An **Ambiguous Schedule Time** requires the user to choose one of the two represented offsets before saving. - A Schedule and its OnTime Backup preserve the user's chosen occurrence of an **Ambiguous Schedule Time**. - Time-zone rules are updated only through an OnTime app release, not through a runtime network request. -- After a time-zone rule update, a future Schedule keeps its intended civil date, time, and Schedule Time Zone while OnTime recalculates its absolute instant and Schedule Notification. +- After a time-zone rule update, a future Schedule keeps its intended civil date, time, and Schedule Time Zone; a changed occurrence is proposed for explicit confirmation before its saved commitment and Schedule Notification are changed. - OnTime identifies future Schedules whose absolute notification time changed because of a time-zone rule update; completed and past Schedules remain unchanged. - The **Local Data Store** is the only authoritative persistence boundary for Durable OnTime Data. - All durable preferences belong to the Local Data Store together with the Local Profile and user content. diff --git a/docs/adr/0021-anchor-schedules-to-a-named-time-zone.md b/docs/adr/0021-anchor-schedules-to-a-named-time-zone.md index 5fe7fcc9..641e1525 100644 --- a/docs/adr/0021-anchor-schedules-to-a-named-time-zone.md +++ b/docs/adr/0021-anchor-schedules-to-a-named-time-zone.md @@ -4,4 +4,17 @@ status: accepted # Anchor schedules to a named time zone -Every Schedule will preserve its intended civil date and time together with a named Schedule Time Zone captured at creation or explicit edit. New schedules default to the current named device zone, while creation and edit flows allow an explicit alternative for future travel. A civil time skipped by an offset transition cannot be saved, while a repeated civil time requires the user to choose one of its represented offsets; that occurrence choice is preserved in the Schedule and backup. Device time-zone changes will trigger notification recalculation but will not move the commitment, and OnTime Backups will preserve the same zoned meaning across devices. Time-zone rules arrive only in app releases; after a rule update, future schedules retain their intended civil time while absolute instants and notifications are recalculated locally, with affected schedules disclosed to the user and past schedules left unchanged. The intended zoned time is the primary display; when the device is in another time zone, OnTime also shows the current-device equivalent and identifies the original zone in notification content. Treating schedules as floating device-local times was rejected because travel, daylight-saving changes, and cross-zone restore could silently change when a real commitment occurs. This adds a time-zone field and migration obligation but makes schedule and notification behavior deterministic and explainable offline. +Every Schedule will preserve its intended civil date and time together with a named Schedule Time Zone captured at creation or explicit edit. New schedules default to the current named device zone, while creation and edit flows allow an explicit alternative for future travel. A civil time skipped by an offset transition cannot be saved, while a repeated civil time requires the user to choose one of its represented offsets; that occurrence choice is preserved in the Schedule and backup. Device time-zone changes will trigger notification recalculation but will not move the commitment, and OnTime Backups will preserve the same zoned meaning across devices. Time-zone rules arrive only in app releases; after a rule update, future schedules retain their intended civil time while changed occurrence instants are calculated locally and disclosed to the user. Persisting a changed future occurrence and arming its new notification require explicit confirmation; past and completed schedules remain unchanged. The intended zoned time is the primary display; when the device is in another time zone, OnTime also shows the current-device equivalent and identifies the original zone in notification content. Treating schedules as floating device-local times was rejected because travel, daylight-saving changes, and cross-zone restore could silently change when a real commitment occurs. This adds a time-zone field and migration obligation but makes schedule and notification behavior deterministic and explainable offline. + + +## A10 clarification — calculation and application are separate + +The A10 decision review distinguishes a device-zone change from a bundled time-zone-rule change. A device-zone change recalculates the home-day boundary and equivalent display; it does not rewrite the commitment, selected offset, or data revision. A rule change may produce a different future occurrence even though its civil fields are unchanged. OnTime calculates that difference automatically, shows the affected commitments, and applies the durable change only after explicit confirmation. Until then, the affected occurrence is not newly started or armed. An obsolete owned reservation is cancelled through the shared delivery owner; failed cancellation remains pending and cannot be replaced with a duplicate reservation. + +An unknown zone, a repeated time without a selected occurrence, or a skipped time never silently becomes UTC or the current device zone. Valid future civil fields with one represented occurrence can be resolved without inventing a device offset. Past explicit offsets and frozen or started occurrences retain their recorded meaning; uncertain past offsets are not replaced with guessed history. Changing a Schedule Time Zone explicitly retains its civil fields and requires choosing and confirming the new occurrence. Recurring expansion's existing gap, count, selected-offset, slot, override, and exclusion rules are unchanged. + +Home “today” uses the current device civil day's half-open instant interval, whose duration can differ from 24 hours. Month-calendar placement retains the commitment's original zone civil date. Comparisons, overlap, ordering, and countdown use resolved occurrence instants. The original zoned commitment remains the primary display, with the current-device equivalent when it differs. + +Civil values must be represented without the device's DST normalization. An internal UTC carrier, if used, carries wall fields and is not itself the commitment instant. Existing civil TEXT and backup-format semantics are retained; this is not an instruction to rewrite stored civil values as UTC instants. Restore corrections happen in staging, followed by complete validation and a new verified preview before final apply. Cancelling that flow changes no active data. + +These are accepted A10 requirements from four recorded root/delegate questions in `plans/audit-2026-09-23/issues/A10.md`; they do not claim that the existing consumers or staging UI already implement them. diff --git a/plans/audit-2026-09-23/issues/A10.md b/plans/audit-2026-09-23/issues/A10.md new file mode 100644 index 00000000..001a15e1 --- /dev/null +++ b/plans/audit-2026-09-23/issues/A10.md @@ -0,0 +1,199 @@ +# [P1][A10] 일정의 civil 시각과 실제 순간을 분리하고 시간대 이동·DST·재시작의 표시와 실행을 일치 + +상태: 전담 4문답과 추가 근거·보충 답변 완료, 본문 동결. 구현·테스트 통과 보고가 아니다. +전담: `/root/grill_a10`; 적용 skill: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +고정 조사 SHA: `818e99c99f9a3892fb2588bd471d2ea21a00f42c`. 조사 중 HEAD가 `0efdd3adb28293d3ff9f6b80bcd9091229384bd5`로 이동했으나 두 commit의 차이는 기존 A14/A15 증거·tracking 문서이며 아래 제품 소스는 동일함을 확인했다. D03 병행 WIP는 본 문서의 구현 성과로 취급하지 않는다. + +## 문제와 사용자 영향 + +한 약속의 준비 시작/알림은 `occurrenceInstantUtc`를 사용하는데 홈 countdown, 지난 일정 판정과 인접 일정은 표시용 `scheduleTime`을 실제 순간처럼 비교한다. 서울에서 만든 약속을 뉴욕에서 보면 남은 시간과 시작 가능 여부가 알림 예약과 달라질 수 있다. offset이 없는 경우의 `scheduleTime.toUtc()`, 알 수 없는 zone의 UTC fallback은 불확실한 데이터를 확정값처럼 소비한다. + +저장된 civil 문자열도 안전하다고 단정할 수 없다. schema 2의 TEXT converter와 form은 device-local `DateTime(...)` 생성자를 사용한다. 뉴욕 기기 DST gap에 해당하는 2026-03-08 02:30을 서울의 wall time으로 편집/왕복할 때 기기 규칙의 정규화가 다른 zone의 합법적 civil 의미를 바꿀 위험이 있다. 현재 자료만으로 실제 사용자 데이터 변형 사고가 입증된 것은 아니며, 서로 다른 TZ process·모바일 검증이 필요하다. + +목표는 일정의 의도된 civil+named zone+명시적 occurrence 선택을 유지하며 모든 실행·비교·표시가 같은 약속을 가리키게 하는 것이다. 모든 날짜 query를 UTC로 일괄 교체하거나 civil 컬럼을 instant로 덮는 작업이 아니다. + +## 고정 소스 근거 + +| 소스 | 확인한 결함/현행 경계 | +|---|---| +| [ScheduleEntity:71](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/domain/entities/schedule_entity.dart#L71) | explicit offset이면 civil fields에서 UTC를 구성하지만 null이면 device-sensitive `scheduleTime.toUtc()`를 사용한다. | +| [ScheduleWithPreparationEntity:49](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/domain/entities/schedule_with_preparation_entity.dart#L49) | 준비 시작은 occurrence instant에서 준비·이동·여유 시간을 차감한다. 같은 실제 순간을 다른 소비자도 사용해야 한다. | +| [ScheduleBloc:244](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/presentation/app/bloc/schedule/schedule_bloc.dart#L244), [835](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/presentation/app/bloc/schedule/schedule_bloc.dart#L835) | upcoming 배제와 준비 중 유효성 판단에 civil scheduleTime을 현재 순간과 직접 비교한다. | +| [Home timer:39](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/presentation/home/bloc/schedule_timer_bloc.dart#L39), [tile:158](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/presentation/home/components/todays_schedule_tile.dart#L158) | civil scheduleTime을 전달받아 now와 차이를 구한다. 실제 intended zone/device equivalent 병렬 표시는 없다. | +| [WeeklySchedulesState](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/presentation/home/bloc/weekly_schedules_state.dart), [nearest upcoming](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/domain/use-cases/get_nearest_upcoming_schedule_use_case.dart) | 오늘은 civil Y/M/D와 device now를 비교한다. nearest는 처음 캡처한 now~now+2days를 civil query에 전달하고 첫 notEnded를 고르므로 zone/자정 변화 뒤 후보 범위도 갱신해야 한다. | +| [인접 일정:54](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/domain/use-cases/get_adjacent_schedules_with_preparation_use_case.dart#L54), [73](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/domain/use-cases/get_adjacent_schedules_with_preparation_use_case.dart#L73), [date/time cubit](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/presentation/schedule_create/schedule_date_time/cubit/schedule_date_time_cubit.dart) | civil 비교·정렬, 같은 시각의 후보 제외, 선택 civil 날짜 전후 제한, previous end의 civil 비교가 혼재한다. | +| [Schedule repository:53](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/data/repositories/schedule_repository_impl.dart#L53) | 달력 civil range query와 materialization이 결합돼 있다. instant 검색 port와 civil 날짜 port의 의미를 분리해야 한다. | +| [CivilDateTimeSqlConverter](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/core/utils/json_converters/duration_json_converters.dart), [schema](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/data/tables/schedules_table.dart) | civil은 이미 TEXT다. converter의 device-local 생성자와 ISO parser의 정규화가 대상이며 schema 변경을 미리 전제하지 않는다. | +| [resolver](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/core/time/civil_time_resolver.dart), [device zone service](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/core/services/local_time_zone_service.dart) | unknown zone 및 native zone 조회 실패를 UTC로 fallback한다. formatUtcOffset은 분까지만 표시하므로 역사 second offset의 저장 보존과 표시 정확성도 검증한다. | +| [form state:159](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/presentation/schedule_create/bloc/schedule_form_state.dart#L159), [date form](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/presentation/schedule_create/schedule_date_time/screens/schedule_date_time_form.dart) | offset null을 device DateTime.timeZoneOffset으로 채우는 fallback이 있다. gap/overlap chip UI는 있으나 명시 zone 선택을 끝까지 연결하는 최소 UX가 필요하다. | +| [backup decode](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/core/backup/backup_service.dart), [recurrence engine](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/lib/domain/recurrence/recurrence_engine.dart) | format1/2 civil parse와 unknown zone 검증은 D05와 공유한다. recurrence는 이미 UTC civil carrier, gap skip, 명시 first/second 선택을 사용한다. 이를 새 일반 일정 정책으로 덮지 않는다. | +| [기존 resolver tests](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/test/core/time/civil_time_resolver_test.dart) | 정상/뉴욕 gap/overlap 3개 unit case가 있다. DB 왕복·zone 이동·다른 process TZ·실제 모바일 알림 일관성의 증거가 아니다. | + +## 기존 계약과 확정 결정 + +[ADR0021](https://github.com/DevKor-github/OnTime-front/blob/818e99c99f9a3892fb2588bd471d2ea21a00f42c/docs/adr/0021-anchor-schedules-to-a-named-time-zone.md)과 CONTEXT의 Schedule Time Zone/Nonexistent Schedule Time/Ambiguous Schedule Time/Preparation Start Moment를 따른다. Android/iOS local-only, bundled tzdb만 사용하며 위치 계정/API/자동 온라인 tz 업데이트를 추가하지 않는다. + +### 실제 순간과 날짜 표면 + +- 예약·준비 시작·countdown·시작 가능/지남·upcoming/지난 순서·겹침은 해석된 occurrence UTC instant를 사용한다. civil carrier를 `isUtc` 때문에 actual instant로 소비하지 않는다. +- 월 달력은 원래 Schedule Time Zone의 civil 날짜에 배치한다. 홈 오늘은 현재 device zone의 해당 civil 날짜 [자정, 다음 자정) instant 구간이며 `start + Duration(days: 1)`로 24시간을 가정하지 않는다. +- 원래 civil+zone을 주표시하고 device equivalent의 날짜/시각이 다르거나 zone 의미가 중요하면 보조표시한다. 서울 2026-09-25 09:00은 뉴욕 2026-09-24 20:00이다. 달력 9/25, 뉴욕 오늘 9/24에 보이되 같은 약속임을 설명한다. +- civil range query와 instant query를 명시적으로 구분한다. device zone 변경·local 자정·resume 때 query/표시 경계를 갱신하고 좁은 civil 후보 검색 때문에 날짜선 양쪽 약속을 누락하지 않는다. 같은 instant의 일반 일정도 충돌 후보에 포함한다. + +### 해석 불가 및 명시 편집 + +- resolved/선택 필요/unknown zone/역사 instant 불확실을 구분한다. unknown zone이나 missing native binding을 UTC로 조용히 대신하지 않는다. +- 미래 offset-null+알려진 zone+유일 occurrence는 zone 규칙으로 해석한다. device offset을 채우지 않는다. overlap은 유효 후보를 명시 선택하고 gap은 다음 유효 civil을 안내하되 자동 이동·저장하지 않는다. +- 미래 explicit offset이 현재 zone 후보에 없으면 재확인 대상이다. 과거/완료 explicit offset은 현 규칙과 달라도 원본 보존하며 unrelated 텍스트 편집/조회만으로 재작성하지 않는다. 과거 null은 역사 instant 불확실을 보존한다. +- 불명확한 일정의 정확 countdown/시작/알림을 막고 설명·수정 진입을 제공한다. 정상 일정과 앱 전체를 막는 DB Recovery로 확대하지 않는다. +- 신규 device zone 조회 실패는 사용자 선택 전 저장을 막는다. 명시 UTC 선택은 가능하다. 자동 탐지 zone과 명시 선택을 화면에서 구분한다. +- 미래 zone 변경은 civil 날짜/시각 유지→새 occurrence 선택→이전/새 instant 및 device 표시 차이 확인→저장이다. instant 유지 때문에 civil을 자동 변경하는 새 기본 동작은 도입하지 않는다. + +### civil 표현과 호환 + +- 가능하면 도메인 `CivilDateTime` 값과 Flutter API용 명시 UTC-carrier adapter를 사용한다. 이는 설계 방향이며 아직 해당 타입이 구현됐다는 뜻이 아니다. +- form/SQLite converter/backup decode/calendar civil 산술 전체를 점검한다. 한층만 고친 뒤 중간 device-local 생성자가 정규화하는 경로를 남기지 않는다. +- 현 TEXT 표현과 format1/2의 의미·초·microseconds·명시 offset을 보존한다. Z를 붙여 의미를 바꾸거나 기존 행을 actual UTC instant로 덮지 않는다. 실제 schema 변경 필요성이 생기면 D04/backup 호환을 다시 검토한다. +- 과거 코드가 이미 정규화한 저장값을 원래 의도로 추측 복구하지 않는다. 서로 다른 TZ process의 실제 SQLite round-trip과 실제 기기 왕복이 필요하다. + +### tzdb 변경·기기 이동·복원의 적용 경계 + +- device zone 변경은 저장된 civil/zone/offset/instant와 durable revision을 바꾸지 않는다. 오늘 경계와 보조표시를 다시 계산하고 A15 reconcile을 요청한다. 표시 변화와 약속 변경을 구별한다. +- bundled tzdb 변경은 미래의 이전/현재 instant 영향 목록을 계산해 알린다. active DB의 변경은 명시 확인 뒤 A08 원자 commit/revision으로 적용한다. 확인 전 stale 일정은 주의 상태이고 새 시작/예약을 보류한다. +- 잘못된 옛 미래 예약은 A15/D03 owner로 취소하며 취소 확인 뒤만 새 예약을 만든다. 실패·unknown은 ownership pending으로 유지하고 중복 예약하지 않는다. 화면 dismiss나 재시작으로 확인됐다고 취급하지 않는다. +- 시작/완료/frozen 회차는 자동 재해석하지 않는다. 현재 실행 중인 preparation의 이미 잡힌 기준을 zone 표시 변경으로 새 run처럼 만들지 않는다. 과거/미래 판정은 작업 시작 시 고정한 현재 UTC와 저장된 완료/명시 occurrence fact를 사용하며 backup cutoff를 현재 시각으로 믿지 않는다. +- restore의 시간대 수정은 원본 파일과 active DB를 바꾸지 않는 staging 후보에서만 수행한다. 명시 선택/수정→전체 관계·자원·시간 검증→새 verified preview→최종 apply다. 취소/검증 실패는 active apply 0이다. **이 staging 수정 UI/검증은 현재 존재하는 기능이 아니며 D05/A08/A09/D02와 함께 구현해야 한다.** +- 기존 recurring 신규 expansion의 gap skip/count/first-second/slot/override/exclusion을 유지한다. 변경 영향은 유한 materialized 미래를 대상으로 하며 무한 series를 전개하지 않는다. gap skip을 이미 보존된 회차의 조용한 삭제 근거로 쓰지 않는다. frozen ownership·전용 준비 정의·완료 이력은 보존한다. +- tzdb version이 없는 format1/2에서 현재 mismatch만으로 원래 잘못된 파일인지 역사적 규칙 변경인지 증명했다고 주장하지 않는다. 영향 확인 상태는 이 불확실성을 설명한다. + +## 문서 정합성 및 ADR 보충 + +Q4 제안은 다음과 같다: **해석과 영향 계산은 자동으로 하되 저장된 미래 occurrence 변경 및 새 알림 예약 적용은 명시 확인 뒤 수행한다.** 자동 적용보다 사용자의 조작이 늘지만 실제 약속 순간이 앱 업데이트만으로 조용히 바뀌는 일을 막고, stale 예약 취소·불명확 gap/overlap·복원 확인을 같은 경계로 설명할 수 있다. 기존 ADR0021에 보충할 만한 실제 제품 trade-off다. 새 범용 timezone/recovery framework ADR는 필요하지 않다. + +root는 Q4 후 `docs/adr/0021-anchor-schedules-to-a-named-time-zone.md`에 위 보충 및 Q1~Q3 구분을 실제 반영했고, CONTEXT의 미래 규칙 변경 문장을 명시 확인으로 좁혔다. 전담 agent는 두 파일을 읽어 일치 여부만 확인했고 수정하지 않았다. 이 문서 변경은 수용된 요구사항이며 제품 구현 완료를 뜻하지 않는다. `CivilDateTime` 타입명·UTC carrier·revision 같은 구현 용어를 제품 glossary로 추가하지 않는다. + +## 의존성 및 구현 순서 + +| 의존성 | A10 책임과 연결 | +|---|---| +| A08 [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | civil/zone/선택 occurrence와 준비·반복 aggregate의 원자 변경 및 읽기 snapshot을 사용한다. stale 편집, 시작/완료, 다른 zone 수정, restore generation과 충돌하면 초안을 보존하고 재검토한다. | +| A09 [#602](https://github.com/DevKor-github/OnTime-front/issues/602), D02 [#604](https://github.com/DevKor-github/OnTime-front/issues/604) | 정상/Recovery 복원의 실제 encrypted staging 후보와 최종 확인 경계를 공유한다. active DB/key/runtime을 검증 전에 바꾸지 않는다. | +| D05 [#606](https://github.com/DevKor-github/OnTime-front/issues/606) | strict civil parse, known IANA/지원 alias, null/명시 offset의 역사 보존, bounded recurring 관계 검증, 전체 재검증과 verified preview를 공유한다. timezone 수정으로 malformed graph/reference나 자원 한도를 우회하지 않는다. | +| D01 [#603](https://github.com/DevKor-github/OnTime-front/issues/603), C01 [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 정상 UI/Recovery shell의 시간대 선택·수정·preview·취소 결과를 typed workflow로 연결한다. 하나의 잘못된 일정 때문에 정상 앱 전체를 DB 손상 상태로 분류하지 않는다. | +| A15 [#594](https://github.com/DevKor-github/OnTime-front/issues/594), D03 [#595](https://github.com/DevKor-github/OnTime-front/issues/595), A14 [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | zone/rule 변경 reconcile도 기존 단일 OS owner·실제 취소 관측·durable 실패 ownership으로 처리한다. 별도 alarm writer를 만들지 않는다. | +| A07 [#599](https://github.com/DevKor-github/OnTime-front/issues/599), A11 [#589](https://github.com/DevKor-github/OnTime-front/issues/589), A12 [#591](https://github.com/DevKor-github/OnTime-front/issues/591) | resolved occurrence 확인과 actual start receipt를 유지한다. zone 변화가 active session·시작 fact·frozen preparation을 덮지 않고 old-generation tap이 새 occurrence를 시작시키지 않는다. | +| D04 [#605](https://github.com/DevKor-github/OnTime-front/issues/605) | 현재 TEXT 호환 변경으로 구현하되 실제 schema 변경 필요성이 생기면 migration/version/역사 fixture 검증을 재검토한다. generated Dart는 commit하지 않는다. | +| U02/U05/T02 | gap/overlap/unknown/영향 확인의 KO/EN 의미·접근성 및 device evidence를 연계한다. 후속 UX/자동화 이슈가 있다는 이유로 A10 필수 UI/실기기 검증을 면제하지 않는다. | + +1. 실제 소비자를 civil 표시/날짜 조회와 instant 비교/실행으로 분류하고 재현 테스트부터 고정한다. +2. 중립 civil 값·adapter 및 typed occurrence resolver를 추가하고 SQLite/form/backup round-trip을 맞춘다. +3. instant query/오늘 경계/정렬/인접·겹침/countdown·시작 판정을 같은 해석 결과로 연결한다. +4. 최소 zone 선택·gap/overlap·unknown·stale 규칙 영향 확인 UI 및 원자 저장을 구현한다. +5. D05/A09/D02 staging 수정→재검증→새 preview와 A15/D03 취소·재예약·재시작을 통합한다. +6. 결정적 unit/actual DB/widget/통합 검증 후 Android/iOS zone 이동·재시작·cross-zone restore를 실행하고 아래 기준에 따라만 완료 처리한다. + +## 상세 검증 및 수용 기준 + +아래 표는 앞으로 실행할 검증 계획이다. 기존 resolver 3개 테스트와 A15 CI 성공을 아래 사례 통과로 대체하지 않는다. 각 사례는 입력 civil/zone/선택 offset, 고정 now UTC, device zone, 예상 UTC/날짜 표시, 실제 read-back을 기록하고 사용자 원문 대신 synthetic fixture를 사용한다. + +| ID | 시나리오 | 기대 결과 및 증거 | +|---|---|---| +| A10-01 | 서울 9/25 09:00 생성 후 뉴욕 이동 | 약속 UTC와 준비 시작/알림 순간 불변; 홈 9/24, 월 달력 9/25; 주/보조 날짜 표시·countdown 일치. | +| A10-02 | 동일 약속을 서울/뉴욕/UTC 기기에서 열기 | 실제 남은 시간·시작 가능/지남·upcoming 순서 동일, 표시만 zone 의미에 맞게 다름. | +| A10-03 | 뉴욕 spring-forward 날 오늘 조회 | device civil 자정~다음 civil 자정의 23시간 구간; 경계 직전/동일/직후 포함 여부 정확. | +| A10-04 | 뉴욕 fall-back 날 오늘 조회 | 25시간 구간, 01:30 첫/둘째 occurrence 모두 각 실제 순간으로 분류·정렬. | +| A10-05 | Asia/Kathmandu 및 Australia/Lord_Howe | 45분 offset 및 30분 DST를 정수 시간 가정 없이 처리하고 정확 offset 표시. | +| A10-06 | device 자정 통과/장시간 foreground/resume | 이전 query의 캡처 now에 고정되지 않고 오늘·upcoming 후보와 countdown 갱신. | +| A10-07 | 날짜선/멀리 떨어진 zone의 인접 일정 | 좁은 civil range 밖에 있어도 instant상 가까운 후보를 누락하지 않음; 과도한 전체 recurrence 확장 없음. | +| A10-08 | 서로 다른 civil 시각이 같은 UTC instant | 일반 일정도 동시 충돌로 포함; self 편집 제외는 유지; duration·준비 겹침은 같은 instant 축. | +| A10-09 | future null offset+known unique zone | zone으로 유일 occurrence 해석, device offset 채움 없음, device 변경 후 결과 동일. | +| A10-10 | unknown zone 또는 native zone 조회 실패 | 조용한 UTC fallback 없음; 신규 저장은 선택 전 차단; 명시 UTC 선택은 가능; 정상 일정 영향 없음. | +| A10-11 | 뉴욕 2026-03-08 02:30 신규 단발 | gap 차단, 다음 유효 civil 안내, 명시 수정 전 저장/예약/시작 없음. | +| A10-12 | 뉴욕 2026-11-01 01:30 신규 단발 | 두 offset을 명시 선택; 05:30Z/06:30Z를 구별; 저장/재시작/backup 왕복 뒤 선택 유지. | +| A10-13 | 과거 explicit offset이 현재 tzdb 후보와 불일치 | history civil/zone/offset/시작·완료 fact 무변경; 조회·unrelated edit로 정규화하지 않음. | +| A10-14 | 과거 null offset | 현재 규칙 유일 해석 가능해도 역사 instant 확정 주장/추정 offset 영속화 없음; 정확성 제한 표시. | +| A10-15 | 미래 explicit offset이 새 규칙상 유일 후보와 다름 | 영향 계산·이전/새 instant 표시, 확인 전 stale/새 예약·시작 보류, 확인 후 원자 적용. | +| A10-16 | 미래 offset이 새 규칙상 overlap/gap | 임의 첫 후보/UTC/gap 자동 이동 없음; 명시 선택/수정 후만 확인 가능. | +| A10-17 | zone만 명시 변경 | civil fields 유지, 새 occurrence 재선택, 이전/새 instant·기기 equivalent 확인; 취소 시 저장 무변경. | +| A10-18 | 실제 SQLite에 서울 2026-03-08 02:30 기록 후 서로 다른 TZ process 읽기 | TEXT와 civil field가 모두 그대로; 뉴욕 device gap의 03:30으로 바뀌지 않음. | +| A10-19 | Pacific/Apia 2011-12-30 carrier·다른 zone의 같은 날짜 | civil carrier 자체는 값 보존; Apia occurrence 해석은 gap으로 구별; device가 날짜를 조용히 건너뛰지 않음. | +| A10-20 | seconds/microseconds·역사 second offset | 저장된 정밀도 보존; offset 표시가 부정확한 분 단위 확정값으로 오인되지 않음; instant 변환 round-trip. | +| A10-21 | format1/2 fixture를 device zone이 다른 환경에서 복원 | civil TEXT 의미와 선택 occurrence·recurring 소유권 보존; `Z` 추가 등 wire 의미 변경 없음. | +| A10-22 | 결정적 tz 규칙 A→B provider 업데이트 | 영향 있는 유한 future만 목록화, past/completed/started/frozen 무변경, 사용자 확인 전후 구분. | +| A10-23 | 규칙 확인 중 다른 편집/시작/완료/restore/reset | A08 baseline/generation conflict로 초안 재검토; 늦은 confirm이 새 자료를 덮지 않음. | +| A10-24 | 기존 OS 예약 취소 실패·unknown·늦은 Future | A15/D03 실제 owner pending 보존, replacement 중복 없음; retry/restart 후 actual read-back 기반 정리. | +| A10-25 | staged timezone 수정 후 graph/reference/size 오류 | 전체 재검증 실패, verified-ready/apply 없음; active DB/key/runtime/알림 변경 0. | +| A10-26 | staging 수정/preview/최종 확인 각 지점 취소 | 원본 backup과 active data 무변경; 후보 정리 결과 별도, old preview 재사용 금지. | +| A10-27 | 반복 gap skip/count/first-second/exclusion/override | 기존 신규 expansion 결과와 소유권 유지; materialized 미래가 조용히 삭제되지 않음; 무한 expansion 없음. | +| A10-28 | active preparation 중 device zone 이동/앱 resume | 새 run·startedAt 덮기·완료 재집계 없음; captured 기준과 실제 elapsed 의미 유지. | +| A10-29 | KO/EN 작은 화면·큰 글자·스크린리더 | 원래 zone/기기 equivalent/첫·둘째 occurrence/차단 이유/수정 action을 구별; 선택 상태·오류가 접근 가능. | +| A10-30 | 실제 Android·iOS 설정에서 zone 변경 후 재시작 | DB civil/offset과 OS 등록 read-back, 홈/달력/countdown·실제 알림 target 순간을 함께 기록. mock으로 대체하지 않음. | +| A10-31 | Android→iOS 및 iOS→Android cross-zone restore | 실제 provider/암호화 staging/preview/확인/DB read-back/미래 알림 일치, 원본/역사 보존. | +| A10-32 | device zone만 바뀐 뒤 데이터 상태 비교 | durable revision 증가 없음, civil/zone/offset/instant 무변경; 화면/오늘 경계·정상 reconcile만 갱신. | + +- [ ] 위 instant 소비자와 civil 소비자 경계가 모두 코드에서 분리되고 `scheduleTime.toUtc()`/device offset/unknown-zone UTC fallback이 잘못된 약속을 만드는 경로를 남기지 않는다. +- [ ] schema2 civil TEXT·format1/2·반복 소유권/보호 회차·nearest 60 알림 cap이 보존된다. 새 표현은 필드/상태의 정밀도나 역사 의미를 잃지 않는다. +- [ ] 실제 SQLite와 서로 다른 TZ process, typed resolver, 실제 UI/DB/OS owner/restore 경계 테스트를 통과한다. source freeze와 명령/실행 환경/결과를 기록한다. +- [ ] 최소 zone 선택·gap/overlap·unknown·rule-change 확인·주/보조 날짜 표시 UI가 KO/EN에서 실제 사용 가능하다. 문서/DTO만으로 완료하지 않는다. +- [ ] 전체 관련 Flutter analyze/test 및 CI가 통과하고 generated Dart를 커밋하지 않는다. 이미 다른 이슈에서 통과한 테스트 숫자를 A10 실행 증거로 재사용하지 않는다. +- [ ] Android/iOS 실제 zone 설정 전환·재시작·알림 및 양방향 cross-zone restore 증거를 확보한다. unavailable device/mock/simulator의 검증 범위를 정확히 구별하고 남으면 issue를 OPEN 유지한다. + +## 범위 밖 + +온라인 tzdb/위치 API, 계정·클라우드 sync, 과거 뜻을 추측하는 데이터 복구, 임의 civil→UTC DB 일괄 변환, 전체 recurrence 정책 재설계, 기존 schedule-notification cap 확장, 다른 앱/OS 전역 알림 제어는 추가하지 않는다. 이 범위 밖 목록은 위에 명시한 실제 UI·staging·모바일 검증을 제외할 근거가 아니다. + +## 실제 전체 문답 + +질문자는 전담 agent, 답변자는 root다. root가 사용자를 대신해 답했으며 사용자의 직접 답변으로 꾸미지 않는다. 질문은 한 번에 하나씩 전송하고 답변 후 다음 질문을 진행했다. + +### Q1 — 실제 순간과 표면별 날짜 의미 + +**Agent 질문 원문** + +> A10 Q1 — ADR0021은 약속의 civil+zone을 고정하지만 현재 홈 countdown/지난 일정 분류/일반 일정 인접·겹침은 device-local scheduleTime을 직접 비교하고, 월 달력은 같은 scheduleTime으로 civil 날짜 범위를 조회합니다. 권고: 약속 instant·준비 시작·남은 시간·시작 가능/지남·홈 upcoming/지난 순서·겹침은 명시적으로 해석된 occurrence UTC 하나로 비교하되, 월 달력 날짜 배치는 각 일정의 원래 Schedule Time Zone civil 날짜를 유지합니다. 홈의 '오늘' 목록은 현재 device civil 날짜의 [자정, 다음 자정) instant 구간(23/25시간 가능)에 속하는 약속으로 정의하고 원래 civil+zone을 주표시, device 날짜/시각을 보조표시합니다. 예: 서울 9/25 09:00은 뉴욕에서 9/24 20:00이며 월 달력 9/25에 남고 뉴욕 홈 9/24 오늘 목록에 표시되어야 합니다. 홈 오늘을 원래 civil 날짜로 유지하는 대안도 가능하므로 이 표면별 날짜 의미와 명확한 zone/date 보조표시를 확정할까요? 같은 instant의 일반 일정도 동시 충돌로 포함하고 인접 후보를 달력의 좁은 civil 날짜 범위만으로 잘라 누락하지 않습니다. + +**Root 답변 원문** + +> Q1 답변: 권고안을 채택한다. 예약·준비 시작·countdown·시작 가능/지남·upcoming/지난 정렬·인접/겹침 판단은 occurrence의 명시적 UTC instant를 공통 기준으로 삼고 device timezone 변경으로 원래 약속 instant를 이동시키지 않는다. 월 달력의 날짜 배치는 원래 Schedule Time Zone의 civil 날짜를 유지한다. 홈 '오늘'은 현재 device zone의 해당 civil 날짜 [자정, 다음 자정) instant 구간으로 정의하되 24시간 Duration을 더해 경계를 만들지 않는다. 일정의 원래 civil+zone을 주표시하고 device 쪽 날짜/시각이 다르거나 zone 의미가 중요한 경우 device 보조표시를 제공해, 제시한 서울 9/25 09:00 ↔ 뉴욕 9/24 20:00 사례에서 달력 9/25/뉴욕 홈 9/24가 모순처럼 보이지 않도록 한다. 같은 instant 일반 일정도 충돌로 포함하며 후보검색을 device의 좁은 civil 날짜만으로 제한하지 않는다. 실제 지금 화면에 보조표시가 없는 것은 현재 결함/구현 필요로 기록한다. 반복 일정의 기존 ADR0021 DST ambiguity 선택과 recurrence civil ownership 계약을 변경하지 말고, A10은 소비자들의 시간 비교·표시 일관성을 맞추는 범위로 한다. UTC 비교를 위해 무작정 기존 civil scheduleTime 컬럼을 UTC로 덮어쓰는 마이그레이션은 승인하지 않으며 필요하면 별도 질문으로 검토한다. + +### Q2 — null offset·unknown zone·명시 편집 + +**Agent 질문 원문** + +> A10 Q2 — null offset·unknown zone 처리와 명시적 편집 정책을 확정해야 합니다. 현재 occurrenceInstantUtc의 offset null은 scheduleTime.toUtc()로 device zone을 섞고, resolver의 unknown zone 및 플랫폼 zone 조회 실패는 UTC로 조용히 대체합니다. 권고: 시간 해석을 resolved/선택 필요/알 수 없는 zone/역사 instant 불확실 상태로 구분합니다. 알려진 zone의 유일 occurrence인 offset-null future는 현재 규칙으로 안전하게 해석하되 현재 device offset을 저장하지 않고, overlap은 명시적 offset 선택, gap은 다음 유효 civil 안내만 하고 자동 이동하지 않습니다. 과거/완료 explicit offset은 현 tzdb와 달라도 원본 보존; 과거 null은 D05대로 불확실성을 보존하여 추정 offset을 역사 확정값으로 쓰지 않습니다. unknown zone·미해결 ambiguity/gap은 해당 일정만 알림/시작·정확 countdown을 막고 수정 안내하며 나머지 정상 일정과 전체 앱은 계속 사용합니다. 신규 zone 조회 실패는 '기기 시간대를 확인할 수 없음'을 표시하고 zone을 사용자가 고르기 전 저장을 막으며, 명시적 UTC 선택은 허용합니다. 미래 편집에서 Schedule Time Zone을 바꿀 때는 civil 날짜/시각을 그대로 유지해 새 zone의 occurrence를 재선택하고 이전/새 약속 instant 및 기기 표시 차이를 저장 전 보여줍니다(instant 유지→civil 자동 변경은 기본 동작으로 도입하지 않음). 기존 값이 없는 zone 선택 UI도 A10에 포함하는 방향을 확정할까요? 현재 schema2 civil 컬럼 일괄 UTC 재작성은 하지 않습니다. + +**Agent 추가 근거 원문** + +> 추가 코드 근거: Schedules.scheduleTime은 이미 TEXT+CivilDateTimeSqlConverter여서 무조건 schema 변경이 필요한 상황은 아닙니다. 그러나 converter fromSql/toSql 및 form의 DateTime(y,m,d,h,m)가 device-local 생성자를 사용합니다. 예를 들어 device America/New_York의 DST gap인 2026-03-08 02:30을 '서울의 02:30' civil로 저장/읽어도 device 규칙이 03:30으로 보정할 위험이 있습니다. 다음 질문에서는 UTC instant로 column을 덮는 대신 civil fields를 timezone-neutral 구조/DateTime.utc carrier로 보존하는 표현 변경 및 고정된 실제 SQLite/서로 다른 TZ process 테스트를 다루겠습니다. HEAD는 조사 중 root 작업으로 0efdd3ad로 이동했으므로 A10 근거는 배정 당시818e99c 고정본을 기준으로 검증하여 기재합니다. + +**Root 답변 원문** + +> Q2 답변: 권고안을 채택한다. 해석 결과를 resolved/선택 필요/unknown zone/역사 instant 불확실로 명시하고 UTC·device zone으로 조용히 대체하지 않는다. 알려진 zone+유일 occurrence인 미래 offset-null은 그 zone 규칙으로 해석할 수 있으나 device offset을 채워 넣지 않는다. overlap은 유효 offset을 사용자가 선택하고 gap은 다음 유효 civil을 제안하되 자동 변경·자동 저장하지 않는다. 과거/완료 explicit offset은 tzdb가 달라져도 저장된 역사값을 보존하고, 과거 offset-null은 불확실성을 보존한다. 해석할 수 없는 일정의 정확 countdown/시작/알림만 차단하고 수정 진입과 설명은 제공하며 정상 일정과 앱 사용을 막지 않는다. 미래의 explicit offset이 현재 해당 zone의 유효 후보에 없는 경우도 재확인 대상으로 분류하여 과거와 같은 보존 규칙을 무조건 적용하지 않는다. 신규 기기 zone 조회 실패는 사용자 zone 선택 전 저장 금지, 명시적 UTC 선택은 허용한다. 이를 실현할 최소 zone 선택 UI와 현재 저장값 보존/편집 UI를 A10 범위에 포함한다. zone 변경은 civil 유지 후 새 occurrence 선택으로 확정하고 이전/새 instant 차이와 device 표시를 확인 후 저장한다. unrelated 텍스트 편집이나 조회만으로 과거 값/offset을 재작성하지 않는다. schema2 civil 컬럼 일괄 UTC 변환·추정값의 확정 저장은 하지 않는다. 자동 탐지된 zone과 사용자가 명시 선택한 zone을 화면에서 구분하되 별도 계정/원격 위치 서비스는 도입하지 않는다. + +### Q3 — device-independent civil 표현 + +**Agent 질문 원문** + +> A10 Q3 — civil 값 자체를 device DST로부터 분리할 최소 표현 변경을 제안합니다. 현 schema2는 TEXT civil ISO이고 converter/form/backup decoder가 device-local DateTime 생성자를 사용하므로 뉴욕 기기에서 서울 2026-03-08 02:30을 다룰 때 device gap 정규화 위험이 있습니다. 권고: 저장된 wall fields가 instant라는 뜻이 아닌 전용 CivilDateTime 값 또는 명확한 UTC-carrier adapter로 모든 civil parse/생성/달력 계산을 중립화하고, 실제 순간은 선택된 offset/zone resolver를 통해서만 얻습니다. TEXT column과 합법 format1/2 의미는 그대로 유지하고 civil 문자열에 Z를 붙여 instant로 재해석하는 마이그레이션은 하지 않습니다. 실제 SQLite round-trip을 서로 다른 TZ의 별도 Dart process에서 수행해 서울 gap 날짜/뉴욕 overlap/Apia 건너뛴 날짜/초·microseconds 보존을 검증합니다. 유효했던 원문 fields를 보존하되 이미 과거 코드가 정규화해 저장한 값을 원래 시각으로 추측 복구하지 않습니다. 이 표현 교정과 회귀 테스트를 A10에 포함할까요? DB schema가 실제로 바뀌어야 한다는 새 근거가 나오면 D04 migration·backup 호환 결정을 먼저 다시 요청합니다. + +**Root 답변 원문** + +> Q3 답변: A10에 포함한다. 저장된 civil wall fields를 device DST 정규화로부터 분리하는 전용 값/adapter가 필요하다. 가능하면 도메인 경계의 CivilDateTime을 도입하고 기존 Flutter DateTime API에는 명시적 UTC-carrier adapter를 쓰되, carrier의 isUtc를 실제 약속 instant라는 뜻으로 소비하지 못하게 변환 함수를 구분한다. UI form/SQLite converter/backup decode/calendar civil 산술 경로를 함께 점검하고 중간층만 바꿔 다시 device-local 생성자로 정규화되지 않도록 한다. 기존 TEXT 표현과 format1/2의 의미, 초·microseconds 및 명시 offset의 역사값을 보존한다. civil 문자열에 Z를 붙여 저장 의미를 바꾸거나 기존 행을 UTC instant로 덮어쓰지 않는다. 실제 SQLite round-trip 및 서로 다른 TZ의 별도 프로세스 테스트를 수용 기준에 포함한다. 서울 시각을 뉴욕 DST gap 기기에서 편집·왕복, 뉴욕 overlap 두 offset, Apia skipped day, device zone 변경 전후 countdown/오늘/달력 구분을 검증한다. 과거 코드가 이미 정규화한 데이터는 원래 의도를 추측 복구하지 않는다. schema 변경 필요성이 새로 생기면 D04 migration/backup 호환 계약과 맞춰 다시 검토한다. + +### Q4 — 규칙 변경 및 적용 시점 + +**Agent 질문 원문** + +> A10 Q4 — tzdb app 업데이트·device zone 변화·복원 확인의 적용 시점을 구체화하겠습니다. Q2의 '미래 stale explicit offset은 재확인'은 ADR0021의 미래 유일 occurrence 재계산·고지 및 D05 verified preview와 맞춰야 합니다. 권고: device zone만 바뀌면 원 약속 offset/instant/자료 revision은 바꾸지 않고 오늘 경계·보조표시를 다시 계산하고 A15 reconcile을 요청합니다. bundled tzdb 규칙이 달라 미래 유일 occurrence가 바뀌면 이전→현재 instant/영향 목록을 제시하며 active DB는 명시 확인 뒤 A08 원자 commit·revision과 A15/D03 cancel-before-rearm으로 적용합니다. 확인 전 해당 stale 일정은 주의 상태·신규 시작/예약 보류, 이미 소유한 잘못된 미래 예약은 안전하게 취소하며 실패는 cleanup pending으로 보존합니다. gap/overlap은 최소 civil 수정/offset 선택 UI로 해결한 뒤 확인; 시작/완료·frozen 회차는 자동 재해석 금지입니다. 백업에서는 원본 파일과 active DB를 바꾸지 않고 staging 후보에서 명시 수정·선택→전체 재검증→새 verified preview→최종 restore로 연결하고, 사용자가 취소하면 apply0입니다. 기존 recurring 신규 materialization의 DST gap skip/count/first-second 규칙·slot/override/exclusion은 그대로 유지하며 유한 materialized 미래의 변경만 처리, 무한확장 금지입니다. 이것은 ADR0021의 '자동 재계산'을 '계산은 자동, durable 변경/새 예약 적용은 명시 확인'으로 좁혀 기록할 필요가 있으므로 root의 승인된 ADR 보충이 필요한 결정을 요청합니다. 동의할까요? 테스트는 가짜 tz provider로 규칙 A→B, 실제 DB/알림 owner 경합, Android/iOS zone 변경·재시작·cross-zone restore 실제 증거를 포함하고 기기 미실행은 완료로 표시하지 않습니다. + +**Agent 추가 근거 원문** + +> 추가 근거(질문 아님): WeeklySchedulesState.todaySchedule은 scheduleTime의 Y/M/D를 DateTime.now()와 비교하고 civil 시각 정렬 후 첫 값을 선택합니다. GetNearestUpcomingScheduleUseCase는 시작 시 now를 한 번 캡처해 civil date query에 [now, now+2days)를 고정하고 첫 notEnded를 택하므로 scalar 비교 교체만으로 timezone/자정 후 후보 누락을 해결하지 못합니다. A10 수용 기준에는 device zone·local 자정·resume 시 query 경계 갱신, crossing civil-date 후보, 동일 instant 충돌을 넣겠습니다. 기존 recurring gap skip은 신규 expansion 정책으로 보존하고, 규칙 변경으로 이미 materialized된 미래 회차를 조용히 삭제하는 근거로 쓰지 않겠습니다. + +**Root 답변 원문** + +> Q4 답변: 동의한다. device zone 변경은 약속 civil/zone/선택 offset/instant와 데이터 revision을 바꾸지 않고 표시·오늘 경계를 재계산한 뒤 기존 A15 reconcile 경로로 동기화한다. bundled tzdb 변경은 기존 저장값과 새 해석값을 비교해 영향 목록을 계산하되 active DB의 미래 occurrence 변경은 명시 확인 뒤 A08의 원자 commit/revision으로 적용한다. 확인 전 stale 일정은 주의 상태이며 새 시작/예약을 보류하고, 기존 잘못된 미래 예약은 A15/D03 owner의 취소 결과를 확인한 뒤에만 재등록한다. 취소 실패는 pending 보존·중복 생성 금지다. 시작/완료/frozen 회차는 자동 재해석하지 않는다. restore는 원본 파일/active DB를 건드리지 않는 staging 수정→전체 재검증→새 verified preview→최종 apply로 연결하고 취소 시 apply 0을 보장한다. 현재 아직 없는 staging 수정 UI/검증은 D05/A08 의존 구현임을 분명히 적고 이미 있다고 서술하지 않는다. 기존 recurring gap skip/count/first-second/slot/override/exclusion 규칙은 바꾸지 않고 영향 있는 유한 materialized 미래만 처리한다. ADR0021은 기존 문맥과 충돌하지 않게 '해석/영향 계산은 자동, 저장값 변경 및 새 예약 적용은 명시 확인' 보충 결정을 A10 문서에 근거와 함께 제안문으로 포함해라. root가 실제 ADR 파일을 반영한다. 테스트는 제시한 결정적 tz 규칙 A→B 및 actual DB/owner/restore 경계, 별도 TZ 프로세스와 기기 검증을 포함하며 미실행 실기기는 완료 처리하지 않는다. 이 답변을 포함한 모든 실제 문답과 코드 근거·세부 수용조건·의존성을 A10.md에 동결해 보고하라. + +**Root 문서 반영 보충 원문** + +> Root가 docs/adr/0021*.md에 Q4 명시 확인 및 Q1~Q3 시간 의미 보충을 실제 반영했고 CONTEXT 기존 timezone-rule update 문장의 자동적용 모호함도 좁혔다. 이는 수용된 요구사항이며 현재 제품 구현 완료를 뜻하지 않는다고 명시했다. A10.md에서 문서 일관성을 참조하되 해당파일은 추가 수정하지 말 것. 원래 Q1~Q4 원문과 root 답변을 그대로 모두 포함해 문서 동결해 달라. From 5f3adf11aaaa3e36d6e4e25c41be6c06e4105480 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 10:36:27 +0900 Subject: [PATCH 33/59] fix: persist alarm ownership and resume verified local reset --- .../kotlin/club/devkor/ontime/MainActivity.kt | 10 + ios/Runner/AppDelegate.swift | 47 ++ .../database/bootstrap_privacy_boundary.dart | 4 + lib/core/database/installation_key_store.dart | 10 +- .../database/local_data_files_native.dart | 6 + lib/core/database/local_data_lifecycle.dart | 98 ++- .../database/local_data_reset_service.dart | 58 +- lib/core/database/local_reset_actions.dart | 115 ++++ lib/core/database/local_reset_protocol.dart | 178 +++++ lib/core/services/alarm_journal_store.dart | 2 + .../services/alarm_journal_store_native.dart | 178 +++++ .../services/alarm_journal_store_web.dart | 3 + .../services/alarm_operation_coordinator.dart | 71 +- .../services/alarm_ownership_journal.dart | 248 +++++++ .../services/alarm_registration_cleanup.dart | 169 ++++- .../alarm_registry_local_data_source.dart | 69 +- .../alarm_registry_repository_impl.dart | 22 +- .../alarm_registry_repository.dart | 12 + .../use-cases/cancel_all_alarms_use_case.dart | 6 + .../use-cases/reconcile_alarms_use_case.dart | 17 +- lib/l10n/app_en.arb | 17 +- lib/l10n/app_ko.arb | 17 +- lib/l10n/app_localizations.dart | 90 +++ lib/l10n/app_localizations_en.dart | 53 ++ lib/l10n/app_localizations_ko.dart | 49 ++ lib/main.dart | 46 +- lib/presentation/my_page/my_data_screen.dart | 19 +- .../screens/local_data_recovery_screen.dart | 28 +- .../screens/local_reset_progress_screen.dart | 133 ++++ .../startup/screens/local_startup_gate.dart | 153 +++++ .../database/alarm_journal_recovery_test.dart | 635 ++++++++++++++++++ .../alarm_replacement_integration_test.dart | 39 +- .../bootstrap_privacy_boundary_test.dart | 23 + .../database/local_reset_actions_test.dart | 171 +++++ .../database/local_reset_protocol_test.dart | 314 +++++++++ .../alarm_ownership_journal_test.dart | 239 +++++++ ...alarm_registry_local_data_source_test.dart | 34 + .../alarm_registry_repository_impl_test.dart | 8 + ...alarm_reconciliation_concurrency_test.dart | 7 + .../cancel_alarms_use_cases_test.dart | 23 +- .../delivery_reconciliation_test.dart | 38 +- .../reconcile_alarms_use_case_test.dart | 8 + .../recurring_alarm_integration_test.dart | 7 + test/helpers/isolated_alarm_owner.dart | 15 + test/helpers/noop_alarm_cleanup.dart | 9 + .../startup/local_reset_recovery_test.dart | 195 ++++++ tool/qa/capture_reset_recovery.dart | 86 +++ tool/qa/reset_journal_crash_probe.dart | 184 +++++ tool/qa/verify_reset_crash_recovery.py | 70 ++ 49 files changed, 3902 insertions(+), 131 deletions(-) create mode 100644 lib/core/database/local_reset_actions.dart create mode 100644 lib/core/database/local_reset_protocol.dart create mode 100644 lib/core/services/alarm_journal_store.dart create mode 100644 lib/core/services/alarm_journal_store_native.dart create mode 100644 lib/core/services/alarm_journal_store_web.dart create mode 100644 lib/core/services/alarm_ownership_journal.dart create mode 100644 lib/presentation/startup/screens/local_reset_progress_screen.dart create mode 100644 lib/presentation/startup/screens/local_startup_gate.dart create mode 100644 test/core/database/alarm_journal_recovery_test.dart create mode 100644 test/core/database/local_reset_actions_test.dart create mode 100644 test/core/database/local_reset_protocol_test.dart create mode 100644 test/core/services/alarm_ownership_journal_test.dart create mode 100644 test/helpers/isolated_alarm_owner.dart create mode 100644 test/presentation/startup/local_reset_recovery_test.dart create mode 100644 tool/qa/capture_reset_recovery.dart create mode 100644 tool/qa/reset_journal_crash_probe.dart create mode 100644 tool/qa/verify_reset_crash_recovery.py diff --git a/android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt b/android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt index c36723b7..ee5418c0 100644 --- a/android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt +++ b/android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt @@ -90,6 +90,16 @@ open class MainActivity : FlutterActivity() { result.success(AlarmLaunchPayload.sanitize(launchPayload)) launchPayload = null } + // AlarmManager has no app-wide registration read-back. Known + // legacy identities are cancelled by the common owner; false + // preserves unknown ownership rather than inventing absence. + "resetCancelAllNativeAlarms" -> result.success(false) + "clearStoredLaunchPayload" -> { + launchPayload = null + // Reset also invalidates notification-plugin launch extras. + intent?.replaceExtras(Bundle()) + result.success(null) + } else -> result.notImplemented() } } diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 74480e19..46e9c40b 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -56,6 +56,8 @@ private let onTimeAlarmLaunchURLHost = "alarm" scheduleNativeAlarm(call, result: result) case "cancelNativeAlarm": cancelNativeAlarm(call, result: result) + case "resetCancelAllNativeAlarms": + resetCancelAllNativeAlarms(result: result) case "getPendingNativeAlarms": getPendingNativeAlarms(call, result: result) case "sanitizeStoredLaunchPayload": @@ -68,6 +70,12 @@ private let onTimeAlarmLaunchURLHost = "alarm" )) case "getLaunchPayload": result(takeStoredAlarmLaunchPayload()) + case "clearStoredLaunchPayload": + let defaults = UserDefaults.standard + defaults.removeObject(forKey: onTimeAlarmLaunchPayloadDefaultsKey) + result(defaults.object(forKey: onTimeAlarmLaunchPayloadDefaultsKey) == nil + ? nil : FlutterError(code: "cleanupUnconfirmed", + message: "Could not confirm launch cleanup.", details: nil)) case "getLocalTimeZone": result(TimeZone.current.identifier) case "excludeFromBackup": @@ -89,6 +97,12 @@ private let onTimeAlarmLaunchURLHost = "alarm" var values = URLResourceValues() values.isExcludedFromBackup = true try url.setResourceValues(values) + guard try url.resourceValues(forKeys: [.isExcludedFromBackupKey]) + .isExcludedFromBackup == true else { + result(FlutterError(code: "backupExclusionUnconfirmed", + message: "Could not confirm local backup exclusion.", details: nil)) + return + } result(nil) } catch { result(FlutterError( @@ -262,6 +276,39 @@ private let onTimeAlarmLaunchURLHost = "alarm" )) } + // Invoked only by explicit full local reset under the shared Dart owner. + // AlarmManager enumerates this app's alarms, including identities no longer + // mappable to a local schedule after registry corruption. + private func resetCancelAllNativeAlarms(result: @escaping FlutterResult) { + #if canImport(AlarmKit) + if #available(iOS 26.0, *) { + do { + let manager = AlarmManager.shared + for alarm in try manager.alarms { + try manager.cancel(id: alarm.id) + } + guard try manager.alarms.isEmpty else { + result(FlutterError(code: "cleanupUnconfirmed", + message: "Native alarm cleanup is unconfirmed.", details: nil)) + return + } + result(true) + } catch { + result(FlutterError(code: "cleanupUnconfirmed", + message: "Native alarm cleanup is unconfirmed.", details: nil)) + } + return + } + #endif + if #available(iOS 26.0, *) { + result(FlutterError(code: "unsupported", + message: "This build cannot confirm native alarm cleanup.", details: nil)) + return + } + // AlarmKit cannot hold registrations on versions before its introduction. + result(true) + } + private func getPendingNativeAlarms(_ call: FlutterMethodCall, result: @escaping FlutterResult) { #if canImport(AlarmKit) if #available(iOS 26.0, *) { diff --git a/lib/core/database/bootstrap_privacy_boundary.dart b/lib/core/database/bootstrap_privacy_boundary.dart index bf808c42..cd006f32 100644 --- a/lib/core/database/bootstrap_privacy_boundary.dart +++ b/lib/core/database/bootstrap_privacy_boundary.dart @@ -10,10 +10,14 @@ class BootstrapPrivacyCleanupFailure implements Exception { Future bootstrapWithPrivacyCleanup({ required Future Function() bootstrap, required Future Function() cleanup, + bool Function(Object error)? shouldCleanup, }) async { try { await bootstrap(); } catch (error, stack) { + if (shouldCleanup != null && !shouldCleanup(error)) { + Error.throwWithStackTrace(error, stack); + } try { await cleanup(); } catch (cleanupError) { diff --git a/lib/core/database/installation_key_store.dart b/lib/core/database/installation_key_store.dart index 2f7f3b3f..cd1be2d8 100644 --- a/lib/core/database/installation_key_store.dart +++ b/lib/core/database/installation_key_store.dart @@ -40,8 +40,10 @@ class InstallationKeyStore { return key; } - Future delete() => _storage.delete( - key: _keyName, - iOptions: _iosOptions, - ); + Future delete() => + _storage.delete(key: _keyName, iOptions: _iosOptions); + + /// Checks absence without creating a replacement key during recovery. + Future exists() async => + await _storage.read(key: _keyName, iOptions: _iosOptions) != null; } diff --git a/lib/core/database/local_data_files_native.dart b/lib/core/database/local_data_files_native.dart index c8f99e82..9f44dadd 100644 --- a/lib/core/database/local_data_files_native.dart +++ b/lib/core/database/local_data_files_native.dart @@ -32,5 +32,11 @@ Future _deleteSqliteFamily(String path) async { for (final suffix in const ['', '-wal', '-shm', '-journal']) { final file = File('$path$suffix'); if (await file.exists()) await file.delete(); + if (await file.exists()) { + throw FileSystemException( + 'Local database removal is unconfirmed', + file.path, + ); + } } } diff --git a/lib/core/database/local_data_lifecycle.dart b/lib/core/database/local_data_lifecycle.dart index b83edc9d..b03317d6 100644 --- a/lib/core/database/local_data_lifecycle.dart +++ b/lib/core/database/local_data_lifecycle.dart @@ -1,86 +1,74 @@ -import 'dart:convert'; - import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:on_time_front/core/database/installation_key_store.dart'; import 'package:on_time_front/core/database/local_data_files.dart'; +import 'package:on_time_front/core/database/local_reset_actions.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; import 'package:on_time_front/core/services/fallback_alarm_notification_service.dart'; -import 'package:on_time_front/core/services/notification_service.dart'; -import 'package:on_time_front/data/models/scheduled_alarm_record_model.dart'; -import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/data/data_sources/alarm_registry_local_data_source.dart'; +import 'package:on_time_front/data/repositories/alarm_registry_repository_impl.dart'; import 'package:shared_preferences/shared_preferences.dart'; +final class LocalResetRecoveryRequired implements Exception { + const LocalResetRecoveryRequired(this.result); + final LocalResetResult result; +} + final class LocalDataLifecycle { static const _storage = FlutterSecureStorage(); static const _cutoverMarker = 'ontime_local_only_cutover_v1'; - static const _resetMarker = 'ontime_local_reset_pending_v1'; - static const _alarmRegistryKey = 'scheduled_alarm_registry'; static const _legacyTokenKeys = ['accessToken', 'refreshToken']; static const _legacyIosOptions = IOSOptions( accessibility: KeychainAccessibility.first_unlock_this_device, ); + static Future? _resetInFlight; static Future bootstrap() async { - if (await _storage.read(key: _resetMarker) != null) { - await _finishInterruptedReset(); - } + final reset = await resumeReset(); + if (reset.recoveryRequired) throw LocalResetRecoveryRequired(reset); if (await _storage.read(key: _cutoverMarker) == null) { await _performOneWayCutover(); } } static Future markResetPending() => - _storage.write(key: _resetMarker, value: 'pending'); + DeviceLocalResetActions().writeMarker(); - static Future _performOneWayCutover() async { - await deleteLocalDatabaseFiles(includeLegacy: true); - await (await SharedPreferences.getInstance()).clear(); - for (final key in _legacyTokenKeys) { - await _storage.delete(key: key); - await _storage.delete(key: key, iOptions: _legacyIosOptions); - } - await _storage.write(key: _cutoverMarker, value: 'complete'); + /// No DB or DI is opened while an interrupted reset remains unconfirmed. + static Future resumeReset() { + final running = _resetInFlight; + if (running != null) return running; + final owner = AlarmOperationCoordinator.shared; + _resetInFlight = owner + .cleanup(() { + final registry = AlarmRegistryRepositoryImpl( + localDataSource: AlarmRegistryLocalDataSourceImpl(), + ); + return LocalResetProtocol( + owner.journal, + AlarmRegistrationCleanup( + registry, + AlarmSchedulerService(), + FallbackAlarmNotificationServiceImpl(), + owner, + ), + DeviceLocalResetActions(), + ).run(begin: false); + }) + .whenComplete(() => _resetInFlight = null); + return _resetInFlight!; } - static Future _finishInterruptedReset() async { - final preferences = await SharedPreferences.getInstance(); - final records = _readAlarmRecords(preferences.getString(_alarmRegistryKey)); - final scheduler = AlarmSchedulerService(); - final fallback = FallbackAlarmNotificationServiceImpl(); - for (final record in records) { - try { - if (record.provider == AlarmProvider.localNotification) { - await fallback.cancelFallbackAlarm(record); - } else if (record.provider != AlarmProvider.none) { - await scheduler.cancelNativeAlarm(record); - } - } catch (_) { - // The marker remains until local files and credentials are removed. - } - } - await NotificationService.instance.cancelAll().catchError((_) {}); + static Future _performOneWayCutover() async { await deleteLocalDatabaseFiles(includeLegacy: true); - await preferences.clear(); - await InstallationKeyStore().delete(); + if (!await (await SharedPreferences.getInstance()).clear()) { + throw StateError('Local cutover preferences cleanup failed'); + } for (final key in _legacyTokenKeys) { await _storage.delete(key: key); await _storage.delete(key: key, iOptions: _legacyIosOptions); } - await _storage.delete(key: _resetMarker); - } - - static List _readAlarmRecords(String? raw) { - if (raw == null || raw.isEmpty) return const []; - try { - return (jsonDecode(raw) as List) - .map( - (item) => ScheduledAlarmRecordModel.fromJson( - item as Map, - ).record, - ) - .toList(); - } catch (_) { - return const []; - } + await _storage.write(key: _cutoverMarker, value: 'complete'); } } diff --git a/lib/core/database/local_data_reset_service.dart b/lib/core/database/local_data_reset_service.dart index c6801736..90a39f4f 100644 --- a/lib/core/database/local_data_reset_service.dart +++ b/lib/core/database/local_data_reset_service.dart @@ -1,12 +1,10 @@ import 'package:injectable/injectable.dart'; import 'package:on_time_front/core/database/database.dart'; import 'package:on_time_front/core/database/installation_key_store.dart'; -import 'package:on_time_front/core/database/local_data_files.dart'; -import 'package:on_time_front/core/database/local_data_lifecycle.dart'; import 'package:on_time_front/core/database/local_data_operation_gate.dart'; -import 'package:on_time_front/core/services/notification_service.dart'; +import 'package:on_time_front/core/database/local_reset_actions.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; import 'package:on_time_front/domain/use-cases/cancel_all_alarms_use_case.dart'; -import 'package:shared_preferences/shared_preferences.dart'; @lazySingleton class LocalDataResetService { @@ -15,26 +13,52 @@ class LocalDataResetService { this._keyStore, this._cancelAllAlarms, { @ignoreParam LocalDataOperationGate? operationGate, - }) : _operationGate = operationGate ?? LocalDataOperationGate.shared; + @ignoreParam LocalResetActions? resetActions, + }) : _operationGate = operationGate ?? LocalDataOperationGate.shared, + _resetActions = resetActions; final AppDatabase _database; final InstallationKeyStore _keyStore; final CancelAllAlarmsUseCase _cancelAllAlarms; final LocalDataOperationGate _operationGate; + final LocalResetActions? _resetActions; + Future? _running; + LocalResetResult? _completed; + bool _databaseClosed = false; - Future reset() => _operationGate.run(_reset, replacesData: true); + /// Repeated taps share the actual operation. A UI timeout never releases it. + Future reset() { + final completed = _completed; + if (completed != null) return Future.value(completed); + final current = _running; + if (current != null) return current; + final next = _operationGate.isInvalidated + ? _reset() + : _operationGate.run(_reset, replacesData: true); + _running = next.then((result) { + if (result.isComplete) _completed = result; + return result; + }).whenComplete(() => _running = null); + return _running!; + } - Future _reset() async { - await LocalDataLifecycle.markResetPending(); - // The durable reset intent is now irrevocable in this process. Even if - // cleanup fails, do not reopen normal writers; bootstrap resumes reset. + Future _reset() { _operationGate.invalidate(); - await _cancelAllAlarms.forDataReplacement(); - await NotificationService.instance.cancelAll(); - await _database.close(); - await deleteLocalDatabaseFiles(includeLegacy: true); - await (await SharedPreferences.getInstance()).clear(); - await _keyStore.delete(); - // The pending marker is intentionally cleared by bootstrap on next launch. + return _cancelAllAlarms.withCleanupOwner( + (cleanup) => LocalResetProtocol( + _cancelAllAlarms.operations.journal, + cleanup, + _resetActions ?? + DeviceLocalResetActions( + keyStore: _keyStore, + closeDatabase: () async { + if (!_databaseClosed) { + await _database.close(); + _databaseClosed = true; + } + }, + ), + ).run(begin: true), + ); } } diff --git a/lib/core/database/local_reset_actions.dart b/lib/core/database/local_reset_actions.dart new file mode 100644 index 00000000..8e82b658 --- /dev/null +++ b/lib/core/database/local_reset_actions.dart @@ -0,0 +1,115 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:on_time_front/core/database/installation_key_store.dart'; +import 'package:on_time_front/core/database/local_data_files.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/core/services/notification_service.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Destructive adapters are shared by live reset and pre-database recovery. +/// A completed stage means its read-back succeeded, not just its API call. +final class DeviceLocalResetActions + implements LocalResetActions, ResetGlobalDeliveryCleanup { + DeviceLocalResetActions({ + FlutterSecureStorage? storage, + InstallationKeyStore? keyStore, + this.closeDatabase, + Future Function()? deleteFiles, + Future Function()? clearDeliveries, + Future Function()? clearNativeDeliveries, + Future Function()? clearLaunch, + }) : storage = storage ?? const FlutterSecureStorage(), + keyStore = keyStore ?? InstallationKeyStore(), + deleteFiles = + deleteFiles ?? (() => deleteLocalDatabaseFiles(includeLegacy: true)), + clearDeliveries = clearDeliveries ?? _clearDeliveries, + clearNativeDeliveries = clearNativeDeliveries ?? _clearNativeDeliveries, + clearLaunch = clearLaunch ?? _clearLaunch; + + static const resetMarker = 'ontime_local_reset_pending_v1'; + static const legacyTokenKeys = ['accessToken', 'refreshToken']; + static const _legacyIosOptions = IOSOptions( + accessibility: KeychainAccessibility.first_unlock_this_device, + ); + final FlutterSecureStorage storage; + final InstallationKeyStore keyStore; + final Future Function()? closeDatabase; + final Future Function() deleteFiles; + final Future Function() clearDeliveries; + final Future Function() clearNativeDeliveries; + bool _allProvidersConfirmedEmpty = false; + @override + bool get allProvidersConfirmedEmpty => _allProvidersConfirmedEmpty; + final Future Function() clearLaunch; + + @override + Future hasMarker() async => + await storage.read(key: resetMarker) != null; + @override + Future writeMarker() async { + await storage.write(key: resetMarker, value: 'pending'); + if (!await hasMarker()) throw const AlarmJournalUnavailable(); + } + + @override + Future removeMarker() async { + await storage.delete(key: resetMarker); + if (await hasMarker()) throw const AlarmJournalUnavailable(); + } + + @override + Future perform(ResetStep step) async { + switch (step) { + case ResetStep.deliveries: + _allProvidersConfirmedEmpty = false; + final nativeConfirmed = await clearNativeDeliveries(); + await clearDeliveries(); + _allProvidersConfirmedEmpty = nativeConfirmed; + case ResetStep.database: + await closeDatabase?.call(); + await deleteFiles(); + case ResetStep.preferences: + final prefs = await SharedPreferences.getInstance(); + if (!await prefs.clear()) throw const AlarmJournalUnavailable(); + await prefs.reload(); + if (prefs.getKeys().isNotEmpty) throw const AlarmJournalUnavailable(); + case ResetStep.key: + await keyStore.delete(); + if (await keyStore.exists()) throw const AlarmJournalUnavailable(); + case ResetStep.credentials: + for (final key in legacyTokenKeys) { + await storage.delete(key: key); + await storage.delete(key: key, iOptions: _legacyIosOptions); + if (await storage.read(key: key) != null || + await storage.read(key: key, iOptions: _legacyIosOptions) != + null) { + throw const AlarmJournalUnavailable(); + } + } + case ResetStep.launch: + await clearLaunch(); + } + } + + static Future _clearNativeDeliveries() async => + await const MethodChannel( + 'on_time_front/native_alarm', + ).invokeMethod('resetCancelAllNativeAlarms') == + true; + + static Future _clearDeliveries() async { + final notifications = NotificationService.instance; + await notifications.cancelAll(); + final remaining = await notifications.observePendingScheduleNotifications(); + if (!remaining.available || remaining.entries.isNotEmpty) { + throw const AlarmJournalUnavailable(); + } + } + + static Future _clearLaunch() async { + await const MethodChannel( + 'on_time_front/native_alarm', + ).invokeMethod('clearStoredLaunchPayload'); + } +} diff --git a/lib/core/database/local_reset_protocol.dart b/lib/core/database/local_reset_protocol.dart new file mode 100644 index 00000000..a630d753 --- /dev/null +++ b/lib/core/database/local_reset_protocol.dart @@ -0,0 +1,178 @@ +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; + +/// A typed receipt says which facts were verified, never exposes raw errors. +final class LocalResetResult { + const LocalResetResult({ + required this.intentRecorded, + required this.completed, + this.isComplete = false, + this.waitingForPlatform = false, + this.recoveryRequired = true, + }); + final bool intentRecorded; + final Set completed; + final bool isComplete; + final bool waitingForPlatform; + final bool recoveryRequired; + bool get dataDeleted => completed.containsAll({ + ResetStep.database, + ResetStep.preferences, + ResetStep.key, + ResetStep.credentials, + }); +} + +abstract interface class LocalResetActions { + Future hasMarker(); + Future writeMarker(); + Future removeMarker(); + Future perform(ResetStep step); +} + +/// Optional receipt for full-app provider cleanup, never used by ordinary OFF. +abstract interface class ResetGlobalDeliveryCleanup { + bool get allProvidersConfirmedEmpty; +} + +/// Runs under AlarmOperationCoordinator's owner, including before DI startup. +/// All progress is recoverable without the encrypted database or its key. +final class LocalResetProtocol { + LocalResetProtocol(this.journal, this.cleanup, this.actions, {this.onIntent}); + final AlarmOwnershipJournal journal; + final AlarmRegistrationCleanup cleanup; + final LocalResetActions actions; + final void Function()? onIntent; + + Future run({required bool begin}) async { + AlarmJournalSnapshot? state; + var intentRecorded = false; + var verifiedSteps = {}; + try { + final marker = await actions.hasMarker(); + try { + state = await journal.read(); + } on AlarmJournalCorrupt { + state = await cleanup.reconstructOwnership(); + if (marker || begin) { + // Only independent secure intent or this explicit confirmation can + // authorize deletion. Never recover completed stages from bad JSON. + state.reset = ResetPhase.pending; + state.unknownOwnership = state.unknownOwnership || marker; + onIntent?.call(); + } + await journal.recoverCorrupt(state); + state = await journal.read(); + } + if (!begin && !marker && state.reset == ResetPhase.none) { + state = await cleanup.resolveLegacyIntegrity(state); + } + intentRecorded = state.reset != ResetPhase.none; + verifiedSteps = Set.of(state.completed); + if (state.reset == ResetPhase.complete) { + // A failed final unlink must never cause user data deletion again. + if (marker) await actions.removeMarker(); + if (await actions.hasMarker()) throw const AlarmJournalUnavailable(); + await journal.removeCompleted(); + return LocalResetResult( + intentRecorded: true, + completed: Set.of(state.completed), + isComplete: true, + recoveryRequired: false, + ); + } + if (!begin && !marker && state.reset != ResetPhase.pending) { + return const LocalResetResult( + intentRecorded: false, + completed: {}, + recoveryRequired: false, + ); + } + if (state.reset != ResetPhase.pending) { + // Migrate remaining registry ownership before any destructive operation. + await journal.remember( + await cleanup.operations.loadRecords(cleanup.registry), + ); + state = await journal.read(); + // Legacy reset cleared preferences even when provider cancellation + // failed. A marker without the new pending journal has lost evidence; + // an empty registry cannot prove that those registrations are gone. + state.unknownOwnership = state.unknownOwnership || marker; + state.reset = ResetPhase.pending; + // An uncertain intent write must not reopen ordinary writers when the + // caller's replacement gate exits. No user data is deleted yet. + onIntent?.call(); + await journal.save(state); + intentRecorded = true; + } else { + onIntent?.call(); + } + await actions.writeMarker(); + if (!await actions.hasMarker()) throw const AlarmJournalUnavailable(); + + // Failed cancellation is a recorded partial result, not a reason to keep + // deleted user content forever. Unreturned calls still hold the owner. + if (!state.completed.contains(ResetStep.deliveries)) { + try { + final report = await cleanup.cancelMatchingReport(); + await actions.perform(ResetStep.deliveries); + if (report.failed == 0) { + state = await journal.read(); + final fullCleanup = actions; + final unknownResolved = + fullCleanup is ResetGlobalDeliveryCleanup && + (fullCleanup as ResetGlobalDeliveryCleanup) + .allProvidersConfirmedEmpty; + if (state.ownership.isEmpty && + (!state.unknownOwnership || unknownResolved)) { + state.unknownOwnership = false; + state.completed.add(ResetStep.deliveries); + await journal.save(state); + verifiedSteps = Set.of(state.completed); + } + } + } catch (_) { + /* Keep the durable pending stage and ownership. */ + } + } + for (final step in ResetStep.values.where( + (s) => s != ResetStep.deliveries, + )) { + state = await journal.read(); + if (state.completed.contains(step)) continue; + try { + await actions.perform(step); + state = await journal.read(); + state.completed.add(step); + await journal.save(state); + verifiedSteps = Set.of(state.completed); + } catch (_) { + /* Retry only unfinished stages on a later run. */ + } + } + state = await journal.read(); + verifiedSteps = Set.of(state.completed); + if (state.completed.length == ResetStep.values.length && + state.ownership.isEmpty && + !state.unknownOwnership) { + state.reset = ResetPhase.complete; + await journal.save(state); + await actions.removeMarker(); + if (await actions.hasMarker()) throw const AlarmJournalUnavailable(); + await journal.removeCompleted(); + return LocalResetResult( + intentRecorded: true, + completed: Set.of(state.completed), + isComplete: true, + recoveryRequired: false, + ); + } + } catch (_) { + // A storage failure is not permission to remove data or invent completion. + } + return LocalResetResult( + intentRecorded: intentRecorded, + completed: verifiedSteps, + ); + } +} diff --git a/lib/core/services/alarm_journal_store.dart b/lib/core/services/alarm_journal_store.dart new file mode 100644 index 00000000..a148ec21 --- /dev/null +++ b/lib/core/services/alarm_journal_store.dart @@ -0,0 +1,2 @@ +export 'alarm_journal_store_native.dart' + if (dart.library.html) 'alarm_journal_store_web.dart'; diff --git a/lib/core/services/alarm_journal_store_native.dart b/lib/core/services/alarm_journal_store_native.dart new file mode 100644 index 00000000..fa8b2abb --- /dev/null +++ b/lib/core/services/alarm_journal_store_native.dart @@ -0,0 +1,178 @@ +import 'dart:io'; +import 'dart:convert'; +import 'package:flutter/services.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'alarm_ownership_journal.dart'; + +/// Atomic replacement on the same filesystem. Flush/read-back is not a claim +/// about directory fsync, sudden power failure, or physical forensic erasure. +final class FileAlarmJournalStore implements RecoverableAlarmJournalStore { + FileAlarmJournalStore( + this.directory, { + this.excludeFromBackup, + this.checkpoint, + }); + final Future Function() directory; + final Future Function(String)? excludeFromBackup; + final Future Function(String)? checkpoint; + + Future _file() async { + final dir = await directory(); + await dir.create(recursive: true); + await excludeFromBackup?.call(dir.path); + return File(p.join(dir.path, 'ownership-v1.json')); + } + + Future _readText(File file) async { + try { + return utf8.decode(await file.readAsBytes()); + } on FormatException { + // FileSystemException deliberately propagates: a transient read failure + // is not evidence that the bytes may be quarantined or replaced. + throw const AlarmJournalCorrupt(); + } + } + + Future> _quarantines(File file) async => [ + await for (final entry in file.parent.list()) + if (entry is File && entry.path.startsWith('${file.path}.corrupt.')) + entry, + ]; + + @override + Future read() async { + final file = await _file(); + if (await File('${file.path}.recovering').exists()) { + throw const AlarmJournalCorrupt(); + } + if (await file.exists()) return _readText(file); + // A valid staged first generation may be promoted. Truncated first writes + // require reconstruction from registry/provider evidence under the owner. + final staged = File('${file.path}.pending'); + if (await staged.exists()) { + final raw = await _readText(staged); + AlarmJournalSnapshot.decode(raw); + await staged.rename(file.path); + if (await _readText(file) != raw) { + throw const AlarmJournalUnavailable(); + } + return raw; + } + if ((await _quarantines(file)).isNotEmpty) { + // A process may have died after quarantining but before publishing the + // reconstructed journal. This is never a clean/new installation. + throw const AlarmJournalCorrupt(); + } + return null; + } + + @override + Future recoverCorrupt(String value) async { + AlarmJournalSnapshot.decode(value); + final file = await _file(); + final staged = File('${file.path}.pending'); + final recovering = File('${file.path}.recovering'); + var corrupt = + await recovering.exists() || + (!await file.exists() && (await _quarantines(file)).isNotEmpty); + // Validate every live candidate before moving anything. A newer version or + // valid JSON with impossible semantics is not an automatic repair target. + for (final candidate in [file, staged]) { + if (!await candidate.exists()) continue; + try { + AlarmJournalSnapshot.decode(await _readText(candidate)); + } on AlarmJournalCorrupt { + corrupt = true; + } + } + if (!corrupt) throw const AlarmJournalUnavailable(); + + await recovering.writeAsString('syntax-recovery-v1', flush: true); + if (await _readText(recovering) != 'syntax-recovery-v1') { + throw const AlarmJournalUnavailable(); + } + await checkpoint?.call('recoveryMarked'); + for (final candidate in [file, staged]) { + if (!await candidate.exists()) continue; + var suffix = 0; + while (await File('${file.path}.corrupt.$suffix').exists()) { + suffix++; + } + await candidate.rename('${file.path}.corrupt.$suffix'); + await checkpoint?.call( + identical(candidate, file) ? 'quarantinedMain' : 'quarantinedPending', + ); + } + await write(value); + if (await _readText(file) != value) throw const AlarmJournalUnavailable(); + await checkpoint?.call('recoveryVerified'); + await recovering.delete(); + await checkpoint?.call('recoveryPublished'); + if (await recovering.exists() || await read() != value) { + throw const AlarmJournalUnavailable(); + } + } + + @override + Future write(String value) async { + final file = await _file(); + final temp = File('${file.path}.pending'); + if (await temp.exists()) { + try { + AlarmJournalSnapshot.decode(await _readText(temp)); + } on AlarmJournalCorrupt { + // An incomplete next generation never authorized an OS side effect. + // Only a verified committed generation makes replacing that stage safe. + if (!await file.exists()) rethrow; + AlarmJournalSnapshot.decode(await _readText(file)); + } + // Unsupported versions and invalid semantic states propagate unchanged; + // an older committed file is not permission to overwrite a future stage. + } + await temp.writeAsString(value, flush: true); + await checkpoint?.call('written'); + if (await temp.readAsString() != value) { + throw const AlarmJournalUnavailable(); + } + await checkpoint?.call('verified'); + await temp.rename(file.path); + await checkpoint?.call('renamed'); + if (await file.readAsString() != value) { + throw const AlarmJournalUnavailable(); + } + await checkpoint?.call('readBack'); + } + + @override + Future remove() async { + final file = await _file(); + final temp = File('${file.path}.pending'); + // Called only for a verified complete reset. Remove retained corruption + // before the complete receipt; a failed unlink cannot look like new state. + for (final quarantine in await _quarantines(file)) { + await quarantine.delete(); + } + final recovering = File('${file.path}.recovering'); + if (await recovering.exists()) await recovering.delete(); + if (await temp.exists()) await temp.delete(); + if (await file.exists()) await file.delete(); + await checkpoint?.call('removed'); + if (await file.exists()) throw const AlarmJournalUnavailable(); + } +} + +AlarmJournalStore createProductAlarmJournalStore() => FileAlarmJournalStore( + () async => Directory( + p.join((await getApplicationSupportDirectory()).path, 'delivery-ownership'), + ), + excludeFromBackup: (path) async { + // Android root/file exclusions already cover application support, including + // cloud and device-transfer. iOS excludes this entire independent directory. + if (Platform.isIOS) { + await const MethodChannel( + 'on_time_front/native_alarm', + ).invokeMethod('excludeFromBackup', {'path': path}); + } + }, +); diff --git a/lib/core/services/alarm_journal_store_web.dart b/lib/core/services/alarm_journal_store_web.dart new file mode 100644 index 00000000..ef9b67c1 --- /dev/null +++ b/lib/core/services/alarm_journal_store_web.dart @@ -0,0 +1,3 @@ +import 'alarm_ownership_journal.dart'; + +AlarmJournalStore createProductAlarmJournalStore() => MemoryAlarmJournalStore(); diff --git a/lib/core/services/alarm_operation_coordinator.dart b/lib/core/services/alarm_operation_coordinator.dart index 6d898a20..c3c7577d 100644 --- a/lib/core/services/alarm_operation_coordinator.dart +++ b/lib/core/services/alarm_operation_coordinator.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'alarm_ownership_journal.dart'; +import 'alarm_journal_store.dart'; import 'package:flutter/foundation.dart'; import 'package:on_time_front/core/database/local_data_operation_gate.dart'; @@ -8,14 +10,17 @@ import 'package:on_time_front/domain/repositories/alarm_registry_repository.dart /// One owner for schedule delivery effects. A timeout never releases this owner: /// only completion of the underlying platform Future releases its queue slot. final class AlarmOperationCoordinator extends ChangeNotifier { - AlarmOperationCoordinator(this.gate) { + AlarmOperationCoordinator(this.gate, {AlarmOwnershipJournal? journal}) + : journal = journal ?? AlarmOwnershipJournal(MemoryAlarmJournalStore()) { gate.addListener(notifyListeners); } static final shared = AlarmOperationCoordinator( LocalDataOperationGate.shared, + journal: AlarmOwnershipJournal(createProductAlarmJournalStore()), ); final LocalDataOperationGate gate; + final AlarmOwnershipJournal journal; Future _tail = Future.value(); final _unpersisted = Expando>(); @@ -48,16 +53,35 @@ final class AlarmOperationCoordinator extends ChangeNotifier { AlarmRegistryRepository registry, ) async { final stored = await registry.loadAll(); - return { + final durable = await journal.read(); + if (registry is AlarmOwnershipIntegrity && + await (registry as AlarmOwnershipIntegrity).hasUnresolvedOwnership() && + !durable.unknownOwnership) { + durable.unknownOwnership = true; + await journal.save(durable); + } + final merged = { for (final record in stored) alarmOwnershipKey(record): record, ...?_unpersisted[registry], - }.values.toList(); + }; + for (final entry in durable.ownership.entries) { + if (entry.value.pending || !merged.containsKey(entry.key)) { + final known = merged[entry.key]; + // The independent journal deliberately has no timing receipt. Preserve + // a known enum from the registry without reviving its private content. + merged[entry.key] = known == null + ? entry.value.record + : ownershipOnly(known); + } + } + return merged.values.toList(); } Future remember( AlarmRegistryRepository registry, List records, ) async { + await journal.remember(records); final memory = _unpersisted[registry] ??= {}; for (final record in records) { memory[alarmOwnershipKey(record)] = ownershipOnly(record); @@ -72,6 +96,10 @@ final class AlarmOperationCoordinator extends ChangeNotifier { ) async { try { await registry.replaceAll(records); + await journal.remember( + records.where((r) => !r.cancellationPending), + pending: false, + ); _unpersisted[registry] = null; } catch (_) { final memory = _unpersisted[registry] ??= {}; @@ -82,14 +110,25 @@ final class AlarmOperationCoordinator extends ChangeNotifier { } } + Future confirmedCancelled( + AlarmRegistryRepository registry, + ScheduledAlarmRecord record, + ) async { + await journal.confirmedCancelled(record); + _unpersisted[registry]?.remove(alarmOwnershipKey(record)); + } + static ScheduledAlarmRecord ownershipOnly(ScheduledAlarmRecord record) => ScheduledAlarmRecord( scheduleId: record.scheduleId, provider: record.provider, nativeAlarmId: record.nativeAlarmId, fallbackNotificationId: record.fallbackNotificationId, - alarmTime: record.alarmTime, - preparationStartTime: record.preparationStartTime, + alarmTime: DateTime.fromMillisecondsSinceEpoch(0, isUtc: true), + preparationStartTime: DateTime.fromMillisecondsSinceEpoch( + 0, + isUtc: true, + ), scheduleFingerprint: '', scheduleTitle: 'OnTime', payload: const {}, @@ -118,6 +157,26 @@ final class AlarmOperationInvalidated implements Exception { const AlarmOperationInvalidated(); } +enum AlarmCleanupStatus { complete, partial, unconfirmed } + +final class AlarmCleanupReport { + const AlarmCleanupReport( + this.targeted, + this.failed, { + this.unknownOwnership = false, + }); + final int targeted; + final int failed; + final bool unknownOwnership; + bool get isComplete => failed == 0 && !unknownOwnership; + AlarmCleanupStatus get status => isComplete + ? AlarmCleanupStatus.complete + : unknownOwnership || failed == targeted + ? AlarmCleanupStatus.unconfirmed + : AlarmCleanupStatus.partial; +} + final class AlarmCleanupIncomplete implements Exception { - const AlarmCleanupIncomplete(); + const AlarmCleanupIncomplete({this.report}); + final AlarmCleanupReport? report; } diff --git a/lib/core/services/alarm_ownership_journal.dart b/lib/core/services/alarm_ownership_journal.dart new file mode 100644 index 00000000..97b24bfb --- /dev/null +++ b/lib/core/services/alarm_ownership_journal.dart @@ -0,0 +1,248 @@ +import 'dart:convert'; + +import 'package:on_time_front/domain/entities/alarm_entities.dart'; + +abstract interface class AlarmJournalStore { + Future read(); + Future write(String value); + Future remove(); +} + +/// Useful for ephemeral development hosts and isolated coordinator instances. +/// The product's shared coordinator always uses the independent file store. +final class MemoryAlarmJournalStore implements AlarmJournalStore { + String? value; + @override + Future read() async => value; + @override + Future write(String value) async { + this.value = value; + } + + @override + Future remove() async { + value = null; + } +} + +class AlarmJournalUnavailable implements Exception { + const AlarmJournalUnavailable(); +} + +/// Only syntax/encoding corruption is eligible for evidence-based recovery. +/// Valid JSON with an unsupported version or invalid state stays untouched. +final class AlarmJournalCorrupt extends AlarmJournalUnavailable { + const AlarmJournalCorrupt(); +} + +final class AlarmJournalUnsupported extends AlarmJournalUnavailable { + const AlarmJournalUnsupported(); +} + +abstract interface class RecoverableAlarmJournalStore + implements AlarmJournalStore { + Future recoverCorrupt(String value); +} + +enum ResetPhase { none, pending, complete } + +enum ResetStep { deliveries, database, preferences, key, credentials, launch } + +/// This projection contains no title, timing, payload, fingerprint or key. +final class AlarmOwnership { + AlarmOwnership(this.record, {required this.pending}); + final ScheduledAlarmRecord record; + final bool pending; + + Map toJson() => { + 'provider': record.provider.name, + 'scheduleId': record.scheduleId, + if (record.provider == AlarmProvider.localNotification) + 'id': record.fallbackNotificationId ?? stableAlarmId(record.scheduleId), + if (record.provider == AlarmProvider.androidAlarmManager) + 'id': record.nativeAlarmId ?? stableAlarmId(record.scheduleId), + 'pending': pending, + }; + + factory AlarmOwnership.fromJson(Map json) { + final provider = AlarmProvider.values.byName(json['provider'] as String); + final id = json['scheduleId']; + final platformId = json['id']; + if (id is! String || + id.isEmpty || + provider == AlarmProvider.none || + json['pending'] is! bool || + (provider != AlarmProvider.iosAlarmKit && + (platformId is! int || + platformId < -2147483648 || + platformId > 2147483647))) { + throw const FormatException('Invalid ownership'); + } + return AlarmOwnership( + ScheduledAlarmRecord( + scheduleId: id, + provider: provider, + nativeAlarmId: provider == AlarmProvider.androidAlarmManager + ? json['id'] as int + : null, + fallbackNotificationId: provider == AlarmProvider.localNotification + ? json['id'] as int + : null, + alarmTime: DateTime.fromMillisecondsSinceEpoch(0, isUtc: true), + preparationStartTime: DateTime.fromMillisecondsSinceEpoch( + 0, + isUtc: true, + ), + scheduleFingerprint: '', + scheduleTitle: 'OnTime', + payload: const {}, + cancellationPending: true, + ), + pending: json['pending'] as bool, + ); + } +} + +final class AlarmJournalSnapshot { + AlarmJournalSnapshot({ + Map? ownership, + this.reset = ResetPhase.none, + this.unknownOwnership = false, + Set? completed, + }) : ownership = ownership ?? {}, + completed = completed ?? {}; + final Map ownership; + ResetPhase reset; + bool unknownOwnership; + final Set completed; + + String encode() => jsonEncode({ + 'version': 1, + 'reset': reset.name, + 'unknownOwnership': unknownOwnership, + 'completed': completed.map((step) => step.name).toList()..sort(), + 'ownership': ownership.values.map((entry) => entry.toJson()).toList(), + }); + + factory AlarmJournalSnapshot.decode(String raw) { + final Object? decoded; + try { + decoded = jsonDecode(raw); + } on FormatException { + throw const AlarmJournalCorrupt(); + } + if (decoded is Map && + decoded['version'] is int && + decoded['version'] != 1) { + throw const AlarmJournalUnsupported(); + } + try { + final json = decoded as Map; + if (json['version'] != 1) throw const FormatException(); + final entries = (json['ownership'] as List) + .map( + (entry) => AlarmOwnership.fromJson(entry as Map), + ) + .toList(); + final completed = (json['completed'] as List) + .map((value) => ResetStep.values.byName(value as String)) + .toList(); + final ownership = {}; + for (final entry in entries) { + final key = alarmOwnershipKey(entry.record); + if (ownership.containsKey(key)) throw const FormatException(); + ownership[key] = entry; + } + if (completed.toSet().length != completed.length) { + throw const FormatException(); + } + if (json['unknownOwnership'] != null && + json['unknownOwnership'] is! bool) { + throw const FormatException(); + } + final result = AlarmJournalSnapshot( + unknownOwnership: json['unknownOwnership'] == true, + reset: ResetPhase.values.byName(json['reset'] as String), + completed: completed.toSet(), + ownership: ownership, + ); + if (result.reset == ResetPhase.none && result.completed.isNotEmpty || + result.completed.contains(ResetStep.deliveries) && + (result.ownership.isNotEmpty || result.unknownOwnership)) { + throw const FormatException(); + } + if (result.reset == ResetPhase.complete && + (result.ownership.isNotEmpty || + result.unknownOwnership || + result.completed.length != ResetStep.values.length)) { + throw const FormatException(); + } + return result; + } catch (_) { + throw const AlarmJournalUnavailable(); + } + } +} + +final class AlarmOwnershipJournal { + AlarmOwnershipJournal(this.store); + final AlarmJournalStore store; + + Future read() async { + final raw = await store.read(); + return raw == null + ? AlarmJournalSnapshot() + : AlarmJournalSnapshot.decode(raw); + } + + /// Called by startup/reset recovery under the same delivery owner. Rebuilt + /// state comes only from independent evidence, never damaged reset receipts. + Future recoverCorrupt(AlarmJournalSnapshot value) async { + final recoverable = store; + if (recoverable is! RecoverableAlarmJournalStore) { + throw const AlarmJournalUnavailable(); + } + final encoded = value.encode(); + AlarmJournalSnapshot.decode(encoded); + await recoverable.recoverCorrupt(encoded); + if (await store.read() != encoded) throw const AlarmJournalUnavailable(); + } + + Future save(AlarmJournalSnapshot value) async { + final encoded = value.encode(); + AlarmJournalSnapshot.decode(encoded); + await store.write(encoded); + if (await store.read() != encoded) throw const AlarmJournalUnavailable(); + } + + Future remember( + Iterable records, { + bool pending = true, + }) async { + final value = await read(); + for (final record in records) { + if (record.provider == AlarmProvider.none) continue; + value.ownership[alarmOwnershipKey(record)] = AlarmOwnership( + record, + pending: pending, + ); + } + await save(value); + } + + /// Only the common cleanup engine may forget a confirmed provider ID. + Future confirmedCancelled(ScheduledAlarmRecord record) async { + final value = await read(); + value.ownership.remove(alarmOwnershipKey(record)); + await save(value); + } + + Future removeCompleted() async { + final value = await read(); + if (value.reset != ResetPhase.complete) { + throw const AlarmJournalUnavailable(); + } + await store.remove(); + if (await store.read() != null) throw const AlarmJournalUnavailable(); + } +} diff --git a/lib/core/services/alarm_registration_cleanup.dart b/lib/core/services/alarm_registration_cleanup.dart index 58aa69b3..3bbb8d31 100644 --- a/lib/core/services/alarm_registration_cleanup.dart +++ b/lib/core/services/alarm_registration_cleanup.dart @@ -1,3 +1,5 @@ +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/domain/entities/delivery_observation.dart'; import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; import 'package:on_time_front/core/services/fallback_alarm_notification_service.dart'; @@ -18,6 +20,161 @@ final class AlarmRegistrationCleanup { final FallbackAlarmNotificationService fallback; final AlarmOperationCoordinator operations; + /// Read-only provider evidence for syntactically damaged journals. This uses + /// the same A14 observation ports/identity rules as reconciliation; it never + /// cancels, schedules, or infers reset progress from damaged bytes. + Future reconstructOwnership({ + Iterable durableRecords = const [], + }) async { + final stored = await registry.loadAll(); + final ownership = {}; + var identityConflict = false; + void retain(ScheduledAlarmRecord record) { + if (record.provider == AlarmProvider.none) return; + final key = alarmOwnershipKey(record); + final previous = ownership[key]; + if (previous != null && previous.record.scheduleId != record.scheduleId) { + identityConflict = true; + return; + } + ownership[key] = AlarmOwnership( + AlarmOperationCoordinator.ownershipOnly(record), + pending: true, + ); + } + + for (final record in [...stored, ...durableRecords]) { + retain(record); + } + + DeliveryObservation notifications; + try { + notifications = await fallback.observePending(); + } catch (_) { + notifications = const DeliveryObservation.unknown(); + } + for (final entry in notifications.entries) { + final id = int.tryParse(entry.id); + final scheduleId = entry.scheduleId; + if (!notifications.available || + scheduleId == null || + scheduleId.isEmpty || + id == null || + id < -2147483648 || + id > 2147483647) { + continue; + } + retain( + ScheduledAlarmRecord( + scheduleId: scheduleId, + provider: AlarmProvider.localNotification, + fallbackNotificationId: id, + alarmTime: DateTime.fromMillisecondsSinceEpoch(0, isUtc: true), + preparationStartTime: DateTime.fromMillisecondsSinceEpoch( + 0, + isUtc: true, + ), + scheduleFingerprint: '', + scheduleTitle: 'OnTime', + payload: const {}, + cancellationPending: true, + ), + ); + } + + DeliveryObservation native; + try { + native = await scheduler.observePendingNativeAlarms( + ownership.values.map((entry) => entry.record.scheduleId).toSet(), + ); + } catch (_) { + native = const DeliveryObservation.unknown(); + } + // The native port only maps IDs the app supplied. Never invert an AlarmKit + // UUID or manufacture a Schedule identity for an unmapped native alarm. + final knownNativeIds = ownership.values + .map((entry) => entry.record.scheduleId) + .toSet(); + for (final entry in native.entries) { + if (!native.available || + native.source != DeliveryObservationSource.iosAlarmKit || + entry.scheduleId == null || + !knownNativeIds.contains(entry.scheduleId) || + entry.id != entry.scheduleId) { + continue; + } + final base = ownership.values.firstWhere( + (known) => known.record.scheduleId == entry.scheduleId, + ); + retain(base.record.copyWith(provider: AlarmProvider.iosAlarmKit)); + } + final allNotificationsMapped = notifications.entries.every((entry) { + final id = int.tryParse(entry.id); + return id != null && ownership.containsKey('localNotification:$id'); + }); + final allNativeMapped = native.entries.every( + (entry) => + entry.scheduleId != null && + entry.id == entry.scheduleId && + ownership.containsKey('iosAlarmKit:${entry.scheduleId}'), + ); + final completeOsEvidence = + notifications.available && + notifications.source == + DeliveryObservationSource.iosNotificationCenter && + notifications.unmappedCount == 0 && + allNotificationsMapped && + native.available && + native.source == DeliveryObservationSource.iosAlarmKit && + native.unmappedCount == 0 && + allNativeMapped && + ownership.values.every( + (entry) => entry.record.provider != AlarmProvider.androidAlarmManager, + ) && + !identityConflict; + return AlarmJournalSnapshot( + ownership: ownership, + unknownOwnership: !completeOsEvidence, + ); + } + + /// Retryable after a crash between journal verification and marker removal. + /// A marker by itself never authorizes deletion of application data. + Future resolveLegacyIntegrity( + AlarmJournalSnapshot state, + ) async { + final integrity = registry; + final hasLegacyMarker = + integrity is AlarmOwnershipIntegrity && + await (integrity as AlarmOwnershipIntegrity).hasUnresolvedOwnership(); + if (!state.unknownOwnership && !hasLegacyMarker) return state; + final observed = await reconstructOwnership( + durableRecords: state.ownership.values.map((entry) => entry.record), + ); + // Save all observed IDs even when platform absence cannot be proved. + state.ownership.addAll(observed.ownership); + state.unknownOwnership = + observed.unknownOwnership || + (hasLegacyMarker && integrity is! RecoverableAlarmOwnershipIntegrity); + await operations.journal.save(state); + state = await operations.journal.read(); + if (!state.unknownOwnership && hasLegacyMarker) { + try { + await (integrity as RecoverableAlarmOwnershipIntegrity) + .clearResolvedOwnership(); + if (await (integrity as RecoverableAlarmOwnershipIntegrity) + .hasUnresolvedOwnership()) { + throw const AlarmJournalUnavailable(); + } + } catch (_) { + state.unknownOwnership = true; + await operations.journal.save(state); + rethrow; + } + } + return state; + } + Future> cancelRecords( List records, ) async { @@ -30,6 +187,7 @@ final class AlarmRegistrationCleanup { } else if (record.provider != AlarmProvider.none) { await scheduler.cancelNativeAlarm(record); } + await operations.confirmedCancelled(registry, record); } catch (_) { failed.add(AlarmOperationCoordinator.ownershipOnly(record)); } @@ -38,6 +196,11 @@ final class AlarmRegistrationCleanup { } Future cancelMatching({String? scheduleId}) async { + final report = await cancelMatchingReport(scheduleId: scheduleId); + if (!report.isComplete) throw AlarmCleanupIncomplete(report: report); + } + + Future cancelMatchingReport({String? scheduleId}) async { final stored = await operations.loadRecords(registry); final targets = stored .where( @@ -50,6 +213,10 @@ final class AlarmRegistrationCleanup { ...stored.where((record) => record.scheduleId != scheduleId), ...failed, ]); - if (failed.isNotEmpty) throw const AlarmCleanupIncomplete(); + return AlarmCleanupReport( + targets.length, + failed.length, + unknownOwnership: (await operations.journal.read()).unknownOwnership, + ); } } diff --git a/lib/data/data_sources/alarm_registry_local_data_source.dart b/lib/data/data_sources/alarm_registry_local_data_source.dart index e8966c98..e0d9b38d 100644 --- a/lib/data/data_sources/alarm_registry_local_data_source.dart +++ b/lib/data/data_sources/alarm_registry_local_data_source.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/repositories/alarm_registry_repository.dart'; import 'dart:convert'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/data/models/scheduled_alarm_record_model.dart'; @@ -12,8 +13,58 @@ abstract interface class AlarmRegistryLocalDataSource { } @Injectable(as: AlarmRegistryLocalDataSource) -class AlarmRegistryLocalDataSourceImpl implements AlarmRegistryLocalDataSource { +class AlarmRegistryLocalDataSourceImpl + implements + AlarmRegistryLocalDataSource, + RecoverableAlarmOwnershipIntegrity { static const _prefsKey = 'scheduled_alarm_registry'; + static const _unknownKey = 'scheduled_alarm_ownership_unknown_v1'; + + @override + Future hasUnresolvedOwnership() async { + final prefs = await SharedPreferences.getInstance(); + // Any value in this reserved marker means uncertainty, including corruption. + return prefs.containsKey(_unknownKey); + } + + @override + Future clearResolvedOwnership() async { + final prefs = await SharedPreferences.getInstance(); + if (!await prefs.remove(_unknownKey)) { + throw StateError('Ownership uncertainty removal failed'); + } + await prefs.reload(); + if (prefs.containsKey(_unknownKey)) { + throw StateError('Ownership uncertainty removal was not persisted'); + } + } + + static Future _markUnknown(SharedPreferences prefs) async { + if (!await prefs.setBool(_unknownKey, true)) { + throw StateError('Ownership uncertainty could not be preserved'); + } + await prefs.reload(); + if (prefs.getBool(_unknownKey) != true) { + throw StateError('Ownership uncertainty was not persisted'); + } + } + + static bool _validIdentity(Map item) { + final provider = item['provider']; + if (provider == AlarmProvider.none.wireValue) return true; + if (!AlarmProvider.values.any((value) => value.wireValue == provider) || + item['scheduleId'] is! String || + (item['scheduleId'] as String).isEmpty) { + return false; + } + final id = + item[provider == AlarmProvider.localNotification.wireValue + ? 'fallbackNotificationId' + : 'nativeAlarmId']; + return provider == AlarmProvider.iosAlarmKit.wireValue || + id == null || + id is int && id >= -2147483648 && id <= 2147483647; + } static ScheduledAlarmRecord _safe(ScheduledAlarmRecord record) { final current = ScheduleWithPreparationEntity.isCurrentIdentity( @@ -64,18 +115,23 @@ class AlarmRegistryLocalDataSourceImpl implements AlarmRegistryLocalDataSource { try { raw = prefs.getString(_prefsKey); } catch (_) { + await _markUnknown(prefs); if (!await prefs.remove(_prefsKey)) { throw StateError('Corrupt registry privacy cleanup failed'); } return const []; } - if (raw == null || raw.isEmpty) return const []; + if (raw == null) return const []; + var unknown = raw.isEmpty; final records = []; try { final decoded = jsonDecode(raw); if (decoded is List) { for (final item in decoded) { - if (item is! Map) continue; + if (item is! Map || !_validIdentity(item)) { + unknown = true; + continue; + } try { records.add(_safe(ScheduledAlarmRecordModel.fromJson(item).record)); } catch (_) { @@ -118,10 +174,15 @@ class AlarmRegistryLocalDataSourceImpl implements AlarmRegistryLocalDataSource { } } } + } else { + unknown = true; } } catch (_) { - /* Corrupt raw content is not retained as a second store. */ + unknown = true; } + // Preserve only uncertainty before scrubbing raw content. Reset migrates + // this receipt into its independent journal before clearing preferences. + if (unknown) await _markUnknown(prefs); final encoded = jsonEncode( records .map((record) => ScheduledAlarmRecordModel(record).toJson()) diff --git a/lib/data/repositories/alarm_registry_repository_impl.dart b/lib/data/repositories/alarm_registry_repository_impl.dart index 3d25ac93..824df2d6 100644 --- a/lib/data/repositories/alarm_registry_repository_impl.dart +++ b/lib/data/repositories/alarm_registry_repository_impl.dart @@ -4,11 +4,31 @@ import 'package:on_time_front/domain/entities/alarm_entities.dart'; import 'package:on_time_front/domain/repositories/alarm_registry_repository.dart'; @Singleton(as: AlarmRegistryRepository) -class AlarmRegistryRepositoryImpl implements AlarmRegistryRepository { +class AlarmRegistryRepositoryImpl + implements AlarmRegistryRepository, RecoverableAlarmOwnershipIntegrity { final AlarmRegistryLocalDataSource localDataSource; AlarmRegistryRepositoryImpl({required this.localDataSource}); + @override + Future hasUnresolvedOwnership() async { + final source = localDataSource; + return source is AlarmOwnershipIntegrity + ? await (source as AlarmOwnershipIntegrity).hasUnresolvedOwnership() + : false; + } + + @override + Future clearResolvedOwnership() async { + final source = localDataSource; + if (source is RecoverableAlarmOwnershipIntegrity) { + await (source as RecoverableAlarmOwnershipIntegrity) + .clearResolvedOwnership(); + } else if (await hasUnresolvedOwnership()) { + throw StateError('Registry cannot resolve ownership uncertainty'); + } + } + @override Future> loadAll() { return localDataSource.loadAll(); diff --git a/lib/domain/repositories/alarm_registry_repository.dart b/lib/domain/repositories/alarm_registry_repository.dart index ea0f2b6e..b3fa1957 100644 --- a/lib/domain/repositories/alarm_registry_repository.dart +++ b/lib/domain/repositories/alarm_registry_repository.dart @@ -11,3 +11,15 @@ abstract interface class AlarmRegistryRepository { Future replaceAll(List records); } + +/// Optional integrity evidence from stores that can detect lost identities. +/// An empty list alone cannot prove that unknown platform registrations ended. +abstract interface class AlarmOwnershipIntegrity { + Future hasUnresolvedOwnership(); +} + +/// Cleared only after complete platform evidence and durable journal read-back. +abstract interface class RecoverableAlarmOwnershipIntegrity + implements AlarmOwnershipIntegrity { + Future clearResolvedOwnership(); +} diff --git a/lib/domain/use-cases/cancel_all_alarms_use_case.dart b/lib/domain/use-cases/cancel_all_alarms_use_case.dart index 5ac526a3..c2986f08 100644 --- a/lib/domain/use-cases/cancel_all_alarms_use_case.dart +++ b/lib/domain/use-cases/cancel_all_alarms_use_case.dart @@ -23,6 +23,12 @@ class CancelAllAlarmsUseCase { final AlarmOperationCoordinator _operations; final AlarmRegistrationCleanup _cleanup; + AlarmOperationCoordinator get operations => _operations; + + Future withCleanupOwner( + Future Function(AlarmRegistrationCleanup) action, + ) => _operations.cleanup(() => action(_cleanup)); + Future call() async { final lease = _operations.capture(); await _operations.run(lease, _cleanup.cancelMatching); diff --git a/lib/domain/use-cases/reconcile_alarms_use_case.dart b/lib/domain/use-cases/reconcile_alarms_use_case.dart index 7ab8723e..99e433ee 100644 --- a/lib/domain/use-cases/reconcile_alarms_use_case.dart +++ b/lib/domain/use-cases/reconcile_alarms_use_case.dart @@ -175,6 +175,8 @@ class ReconcileAlarmsUseCase { if (!settings.alarmsEnabled) { final stored = await _operations.loadRecords(_registryRepository); + final unknownOwnership = + (await _operations.journal.read()).unknownOwnership; final observed = await _observeFallback(); final records = [...stored, ..._fallbackOrphans(observed, stored)]; final nativeObserved = await _observeNative(records, capabilities); @@ -192,6 +194,7 @@ class ReconcileAlarmsUseCase { final result = _result( status: failedCancellations.isEmpty && + !unknownOwnership && !nativeUncertain && (observed.available || capabilities.fallbackProvider != @@ -200,7 +203,7 @@ class ReconcileAlarmsUseCase { : AlarmReconciliationStatus.partial, failures: [ ..._cancellationFailures(failedCancellations), - if (nativeUncertain) _observationFailure(), + if (nativeUncertain || unknownOwnership) _observationFailure(), if (!observed.available && capabilities.fallbackProvider == AlarmProvider.localNotification) _observationFailure(), @@ -279,6 +282,18 @@ class ReconcileAlarmsUseCase { ); final storedRecords = await _operations.loadRecords(_registryRepository); + if ((await _operations.journal.read()).unknownOwnership) { + lease.check(); + return _result( + status: AlarmReconciliationStatus.partial, + failures: [_observationFailure()], + capabilities: capabilities, + scheduleWindowStart: scheduleWindowStart, + scheduleWindowEnd: scheduleWindowEnd, + alarmCoverageStart: alarmCoverageStart, + alarmCoverageEnd: alarmCoverageEnd, + ); + } final fallbackObservation = await _observeFallback(); final existingRecords = [ ...storedRecords, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index dc30f865..7b5dc508 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,4 +1,17 @@ { + "startupRecoveryTitle": "Unable to start OnTime", + "startupRecoveryBody": "OnTime could not verify local data. Please try again shortly.", + "startupRetryAction": "Try again", + "restartRequiredBody": "Close the app completely, then reopen it.", + "resetInProgressTitle": "Resetting local data", + "resetInProgressBody": "Checking notification and local data cleanup.", + "resetWaitingBody": "Waiting for the device to finish a notification operation. Reset is not complete. Reopening the app will resume cleanup.", + "resetDeletedPendingBody": "Local data was deleted, but some notification cleanup is still unconfirmed.", + "resetIncompleteBody": "Reset is not complete. Only unconfirmed cleanup steps will be retried.", + "resetNoIntentBody": "The reset record could not be verified. Reset has not been reported as complete. Please try again.", + "resetCompleteTitle": "Local data reset complete", + "resetCompleteBody": "Local data and notification cleanup were verified. Close the app completely and reopen it to start again.", + "resetRetryAction": "Retry cleanup", "notificationCleanupNeededStatus": "Off · cancellation needs checking", "calendarTitle": "Calendar", "@calendarTitle": { @@ -609,5 +622,7 @@ "notificationApproximateStatus": "Notification · approximate timing", "notificationMixedTimingStatus": "Notification · some approximate timing", "notificationIncompleteStatus": "Notification scheduling incomplete or needs checking", - "notificationTimingSettings": "Timing settings" + "notificationTimingSettings": "Timing settings", + "startupLoadingBody": "Checking the data stored on this device.", + "startupWaitingBody": "Waiting for the device to respond. Please wait until the check finishes." } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 6c166e99..e961cd44 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -1,4 +1,17 @@ { + "startupRecoveryTitle": "앱을 시작할 수 없습니다", + "startupRecoveryBody": "로컬 데이터를 확인하는 중 문제가 발생했습니다. 잠시 후 다시 시도해 주세요.", + "startupRetryAction": "다시 시도", + "restartRequiredBody": "앱을 완전히 닫은 뒤 다시 열어 주세요.", + "resetInProgressTitle": "로컬 데이터 초기화 중", + "resetInProgressBody": "알림과 로컬 데이터의 정리 상태를 확인하고 있습니다.", + "resetWaitingBody": "기기의 알림 작업 응답을 기다리고 있습니다. 아직 초기화가 완료되지 않았습니다. 앱을 다시 열면 정리를 이어갑니다.", + "resetDeletedPendingBody": "기기 내 데이터는 삭제됐지만 일부 알림 정리는 아직 확인되지 않았습니다.", + "resetIncompleteBody": "초기화가 끝나지 않았습니다. 확인되지 않은 정리 단계만 다시 시도합니다.", + "resetNoIntentBody": "초기화 기록을 확인하지 못해 완료로 처리하지 않았습니다. 다시 시도해 주세요.", + "resetCompleteTitle": "로컬 데이터 초기화 완료", + "resetCompleteBody": "로컬 데이터와 알림 정리를 확인했습니다. 새로 시작하려면 앱을 완전히 닫고 다시 열어 주세요.", + "resetRetryAction": "정리 다시 시도", "notificationCleanupNeededStatus": "꺼짐 · 알림 취소 확인 필요", "calendarTitle": "캘린더", "error": "오류", @@ -206,5 +219,7 @@ "notificationApproximateStatus": "알림 · 근사 시각", "notificationMixedTimingStatus": "알림 · 일부 근사 시각", "notificationIncompleteStatus": "알림 예약 일부 실패 또는 확인 필요", - "notificationTimingSettings": "정확한 시각 설정하기" + "notificationTimingSettings": "정확한 시각 설정하기", + "startupLoadingBody": "기기에 저장된 데이터를 확인하고 있습니다.", + "startupWaitingBody": "기기의 응답을 기다리고 있습니다. 확인이 끝날 때까지 잠시 기다려 주세요." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index cc12222e..14564335 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -98,6 +98,84 @@ abstract class AppLocalizations { Locale('ko'), ]; + /// No description provided for @startupRecoveryTitle. + /// + /// In en, this message translates to: + /// **'Unable to start OnTime'** + String get startupRecoveryTitle; + + /// No description provided for @startupRecoveryBody. + /// + /// In en, this message translates to: + /// **'OnTime could not verify local data. Please try again shortly.'** + String get startupRecoveryBody; + + /// No description provided for @startupRetryAction. + /// + /// In en, this message translates to: + /// **'Try again'** + String get startupRetryAction; + + /// No description provided for @restartRequiredBody. + /// + /// In en, this message translates to: + /// **'Close the app completely, then reopen it.'** + String get restartRequiredBody; + + /// No description provided for @resetInProgressTitle. + /// + /// In en, this message translates to: + /// **'Resetting local data'** + String get resetInProgressTitle; + + /// No description provided for @resetInProgressBody. + /// + /// In en, this message translates to: + /// **'Checking notification and local data cleanup.'** + String get resetInProgressBody; + + /// No description provided for @resetWaitingBody. + /// + /// In en, this message translates to: + /// **'Waiting for the device to finish a notification operation. Reset is not complete. Reopening the app will resume cleanup.'** + String get resetWaitingBody; + + /// No description provided for @resetDeletedPendingBody. + /// + /// In en, this message translates to: + /// **'Local data was deleted, but some notification cleanup is still unconfirmed.'** + String get resetDeletedPendingBody; + + /// No description provided for @resetIncompleteBody. + /// + /// In en, this message translates to: + /// **'Reset is not complete. Only unconfirmed cleanup steps will be retried.'** + String get resetIncompleteBody; + + /// No description provided for @resetNoIntentBody. + /// + /// In en, this message translates to: + /// **'The reset record could not be verified. Reset has not been reported as complete. Please try again.'** + String get resetNoIntentBody; + + /// No description provided for @resetCompleteTitle. + /// + /// In en, this message translates to: + /// **'Local data reset complete'** + String get resetCompleteTitle; + + /// No description provided for @resetCompleteBody. + /// + /// In en, this message translates to: + /// **'Local data and notification cleanup were verified. Close the app completely and reopen it to start again.'** + String get resetCompleteBody; + + /// No description provided for @resetRetryAction. + /// + /// In en, this message translates to: + /// **'Retry cleanup'** + String get resetRetryAction; + /// No description provided for @notificationCleanupNeededStatus. /// /// In en, this message translates to: @@ -973,6 +1051,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Timing settings'** String get notificationTimingSettings; + + /// No description provided for @startupLoadingBody. + /// + /// In en, this message translates to: + /// **'Checking the data stored on this device.'** + String get startupLoadingBody; + + /// No description provided for @startupWaitingBody. + /// + /// In en, this message translates to: + /// **'Waiting for the device to respond. Please wait until the check finishes.'** + String get startupWaitingBody; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 086649d0..0e283fe4 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -8,6 +8,52 @@ import 'app_localizations.dart'; class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); + @override + String get startupRecoveryTitle => 'Unable to start OnTime'; + + @override + String get startupRecoveryBody => + 'OnTime could not verify local data. Please try again shortly.'; + + @override + String get startupRetryAction => 'Try again'; + + @override + String get restartRequiredBody => 'Close the app completely, then reopen it.'; + + @override + String get resetInProgressTitle => 'Resetting local data'; + + @override + String get resetInProgressBody => + 'Checking notification and local data cleanup.'; + + @override + String get resetWaitingBody => + 'Waiting for the device to finish a notification operation. Reset is not complete. Reopening the app will resume cleanup.'; + + @override + String get resetDeletedPendingBody => + 'Local data was deleted, but some notification cleanup is still unconfirmed.'; + + @override + String get resetIncompleteBody => + 'Reset is not complete. Only unconfirmed cleanup steps will be retried.'; + + @override + String get resetNoIntentBody => + 'The reset record could not be verified. Reset has not been reported as complete. Please try again.'; + + @override + String get resetCompleteTitle => 'Local data reset complete'; + + @override + String get resetCompleteBody => + 'Local data and notification cleanup were verified. Close the app completely and reopen it to start again.'; + + @override + String get resetRetryAction => 'Retry cleanup'; + @override String get notificationCleanupNeededStatus => 'Off · cancellation needs checking'; @@ -516,4 +562,11 @@ class AppLocalizationsEn extends AppLocalizations { @override String get notificationTimingSettings => 'Timing settings'; + + @override + String get startupLoadingBody => 'Checking the data stored on this device.'; + + @override + String get startupWaitingBody => + 'Waiting for the device to respond. Please wait until the check finishes.'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index f3c9d8c0..89b113fa 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -8,6 +8,49 @@ import 'app_localizations.dart'; class AppLocalizationsKo extends AppLocalizations { AppLocalizationsKo([String locale = 'ko']) : super(locale); + @override + String get startupRecoveryTitle => '앱을 시작할 수 없습니다'; + + @override + String get startupRecoveryBody => + '로컬 데이터를 확인하는 중 문제가 발생했습니다. 잠시 후 다시 시도해 주세요.'; + + @override + String get startupRetryAction => '다시 시도'; + + @override + String get restartRequiredBody => '앱을 완전히 닫은 뒤 다시 열어 주세요.'; + + @override + String get resetInProgressTitle => '로컬 데이터 초기화 중'; + + @override + String get resetInProgressBody => '알림과 로컬 데이터의 정리 상태를 확인하고 있습니다.'; + + @override + String get resetWaitingBody => + '기기의 알림 작업 응답을 기다리고 있습니다. 아직 초기화가 완료되지 않았습니다. 앱을 다시 열면 정리를 이어갑니다.'; + + @override + String get resetDeletedPendingBody => + '기기 내 데이터는 삭제됐지만 일부 알림 정리는 아직 확인되지 않았습니다.'; + + @override + String get resetIncompleteBody => '초기화가 끝나지 않았습니다. 확인되지 않은 정리 단계만 다시 시도합니다.'; + + @override + String get resetNoIntentBody => '초기화 기록을 확인하지 못해 완료로 처리하지 않았습니다. 다시 시도해 주세요.'; + + @override + String get resetCompleteTitle => '로컬 데이터 초기화 완료'; + + @override + String get resetCompleteBody => + '로컬 데이터와 알림 정리를 확인했습니다. 새로 시작하려면 앱을 완전히 닫고 다시 열어 주세요.'; + + @override + String get resetRetryAction => '정리 다시 시도'; + @override String get notificationCleanupNeededStatus => '꺼짐 · 알림 취소 확인 필요'; @@ -480,4 +523,10 @@ class AppLocalizationsKo extends AppLocalizations { @override String get notificationTimingSettings => '정확한 시각 설정하기'; + + @override + String get startupLoadingBody => '기기에 저장된 데이터를 확인하고 있습니다.'; + + @override + String get startupWaitingBody => '기기의 응답을 기다리고 있습니다. 확인이 끝날 때까지 잠시 기다려 주세요.'; } diff --git a/lib/main.dart b/lib/main.dart index d851ae0b..2886dc72 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,28 +5,46 @@ import 'package:flutter/services.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:on_time_front/core/di/di_setup.dart'; import 'package:on_time_front/core/database/local_data_lifecycle.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/database/local_data_reset_service.dart'; import 'package:on_time_front/core/logging/app_logger.dart'; import 'package:on_time_front/core/services/device_info_service/shared.dart'; import 'package:on_time_front/core/services/notification_service.dart'; import 'package:on_time_front/core/services/notification_tap_router.dart'; import 'package:on_time_front/presentation/app/screens/app.dart'; +import 'package:on_time_front/presentation/startup/screens/local_startup_gate.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); AppLogger.configureFlutterDebugPrint(); - await HardwareKeyboard.instance.syncKeyboardState().catchError((_) {}); - await initializeDateFormatting(); - await bootstrapWithPrivacyCleanup( - bootstrap: LocalDataLifecycle.bootstrap, - cleanup: RuntimePrivacyMigration.clearLegacyWithoutDatabase, + runApp( + LocalStartupGate( + prepare: () async { + await HardwareKeyboard.instance.syncKeyboardState().catchError((_) {}); + await initializeDateFormatting(); + // Recovery owns its own cleanup evidence. A failed reset must not fall + // into an unrelated best-effort cleanup that discards its registry. + await bootstrapWithPrivacyCleanup( + bootstrap: LocalDataLifecycle.bootstrap, + cleanup: RuntimePrivacyMigration.clearLegacyWithoutDatabase, + shouldCleanup: (error) => error is! LocalResetRecoveryRequired, + ); + }, + retryReset: LocalDataLifecycle.resumeReset, + ready: () { + configureDependencies(); + NotificationService.instance.configureDelegate( + notificationTapRouter: getIt.get(), + ); + AppLogger.debug( + 'Device standalone mode=${DeviceInfoService.isInStandaloneMode}', + ); + return ResetAwareApp( + gate: LocalDataOperationGate.shared, + reset: () => getIt().reset(), + child: const App(), + ); + }, + ), ); - configureDependencies(); - NotificationService.instance.configureDelegate( - notificationTapRouter: getIt.get(), - ); - - AppLogger.debug( - 'Device standalone mode=${DeviceInfoService.isInStandaloneMode}', - ); - runApp(App()); } diff --git a/lib/presentation/my_page/my_data_screen.dart b/lib/presentation/my_page/my_data_screen.dart index 6fdd9d06..138189e4 100644 --- a/lib/presentation/my_page/my_data_screen.dart +++ b/lib/presentation/my_page/my_data_screen.dart @@ -1,5 +1,6 @@ +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/presentation/startup/screens/local_reset_progress_screen.dart'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:on_time_front/core/backup/backup_password.dart'; import 'package:on_time_front/core/backup/backup_service.dart'; import 'package:on_time_front/core/database/local_data_reset_service.dart'; @@ -15,6 +16,7 @@ class MyDataScreen extends StatefulWidget { class _MyDataScreenState extends State { bool _busy = false; + LocalResetResult? _resetResult; BackupFreshnessStatus? _freshness; @override @@ -30,6 +32,13 @@ class _MyDataScreenState extends State { @override Widget build(BuildContext context) { + final resetResult = _resetResult; + if (resetResult != null) { + return LocalResetProgressScreen( + initialResult: resetResult, + operation: getIt().reset, + ); + } final freshness = _freshness; return Scaffold( appBar: AppBar(title: const Text('내 데이터')), @@ -158,7 +167,7 @@ class _MyDataScreenState extends State { ], ), ); - if (confirmed != true) return; + if (confirmed != true || !mounted) return; await getIt().applyRestore(candidate); await getIt()(); if (!mounted) return; @@ -193,11 +202,11 @@ class _MyDataScreenState extends State { ], ), ); - if (confirmed != true) return; + if (confirmed != true || !mounted) return; setState(() => _busy = true); try { - await getIt().reset(); - if (mounted) context.go('/resetComplete'); + final result = await getIt().reset(); + if (mounted) setState(() => _resetResult = result); } catch (error) { if (!mounted) return; setState(() => _busy = false); diff --git a/lib/presentation/startup/screens/local_data_recovery_screen.dart b/lib/presentation/startup/screens/local_data_recovery_screen.dart index 47b130ac..5deb6fe0 100644 --- a/lib/presentation/startup/screens/local_data_recovery_screen.dart +++ b/lib/presentation/startup/screens/local_data_recovery_screen.dart @@ -1,6 +1,7 @@ +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/presentation/startup/screens/local_reset_progress_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; import 'package:on_time_front/core/database/local_data_reset_service.dart'; import 'package:on_time_front/core/di/di_setup.dart'; import 'package:on_time_front/presentation/app/bloc/auth/auth_bloc.dart'; @@ -15,9 +16,17 @@ class LocalDataRecoveryScreen extends StatefulWidget { class _LocalDataRecoveryScreenState extends State { bool _busy = false; + LocalResetResult? _resetResult; @override Widget build(BuildContext context) { + final resetResult = _resetResult; + if (resetResult != null) { + return LocalResetProgressScreen( + initialResult: resetResult, + operation: getIt().reset, + ); + } return Scaffold( body: SafeArea( child: Center( @@ -93,9 +102,20 @@ class _LocalDataRecoveryScreenState extends State { ], ), ); - if (confirmed != true) return; + if (confirmed != true || !mounted) return; setState(() => _busy = true); - await getIt().reset(); - if (mounted) context.go('/resetComplete'); + try { + final result = await getIt().reset(); + if (mounted) setState(() => _resetResult = result); + } catch (_) { + if (!mounted) return; + setState(() { + _busy = false; + _resetResult = const LocalResetResult( + intentRecorded: false, + completed: {}, + ); + }); + } } } diff --git a/lib/presentation/startup/screens/local_reset_progress_screen.dart b/lib/presentation/startup/screens/local_reset_progress_screen.dart new file mode 100644 index 00000000..6a7fc0b4 --- /dev/null +++ b/lib/presentation/startup/screens/local_reset_progress_screen.dart @@ -0,0 +1,133 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/l10n/app_localizations.dart'; + +/// A pending platform Future remains owned by the service after the UI's wait +/// notice. It is neither canceled nor replaced by a second reset operation. +class LocalResetProgressScreen extends StatefulWidget { + const LocalResetProgressScreen({ + super.key, + required this.operation, + this.initialResult, + this.waitNoticeAfter = const Duration(seconds: 10), + }); + final Future Function() operation; + final LocalResetResult? initialResult; + final Duration waitNoticeAfter; + @override + State createState() => + _LocalResetProgressScreenState(); +} + +class _LocalResetProgressScreenState extends State { + LocalResetResult? _result; + bool _busy = false; + bool _waiting = false; + Timer? _timer; + @override + void initState() { + super.initState(); + _result = widget.initialResult; + if (_result == null) unawaited(_run()); + } + + Future _run() async { + if (_busy) return; + setState(() { + _busy = true; + _waiting = false; + }); + _timer = Timer(widget.waitNoticeAfter, () { + if (mounted && _busy) setState(() => _waiting = true); + }); + try { + final result = await widget.operation(); + if (mounted) setState(() => _result = result); + } catch (_) { + if (mounted) { + setState( + () => _result ??= const LocalResetResult( + intentRecorded: false, + completed: {}, + ), + ); + } + } finally { + _timer?.cancel(); + if (mounted) { + setState(() { + _busy = false; + _waiting = false; + }); + } + } + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final result = _result; + final complete = result?.isComplete == true; + final body = _waiting + ? l10n.resetWaitingBody + : _busy + ? l10n.resetInProgressBody + : complete + ? l10n.resetCompleteBody + : result?.dataDeleted == true + ? l10n.resetDeletedPendingBody + : result?.intentRecorded == true + ? l10n.resetIncompleteBody + : l10n.resetNoIntentBody; + return PopScope( + canPop: false, + child: Scaffold( + body: SafeArea( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 440), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + complete + ? Icons.check_circle_outline + : Icons.storage_rounded, + size: 56, + ), + const SizedBox(height: 20), + Text( + complete + ? l10n.resetCompleteTitle + : l10n.resetInProgressTitle, + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + Text(body, textAlign: TextAlign.center), + const SizedBox(height: 24), + if (_busy) const CircularProgressIndicator(), + if (!_busy && !complete) + FilledButton( + onPressed: _run, + child: Text(l10n.resetRetryAction), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/startup/screens/local_startup_gate.dart b/lib/presentation/startup/screens/local_startup_gate.dart new file mode 100644 index 00000000..0f92757a --- /dev/null +++ b/lib/presentation/startup/screens/local_startup_gate.dart @@ -0,0 +1,153 @@ +import 'package:on_time_front/presentation/shared/theme/theme.dart'; +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:on_time_front/core/database/local_data_lifecycle.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/l10n/app_localizations.dart'; +import 'local_reset_progress_screen.dart'; + +/// Minimal pre-DI recovery surface. The broader D01 startup transaction remains +/// separate; a failure during DI construction requires a restart, not reuse. +class LocalStartupGate extends StatefulWidget { + const LocalStartupGate({ + super.key, + required this.prepare, + required this.ready, + required this.retryReset, + }); + final Future Function() prepare; + final Widget Function() ready; + final Future Function() retryReset; + @override + State createState() => _LocalStartupGateState(); +} + +class _LocalStartupGateState extends State { + Widget? _ready; + Object? _failure; + bool _busy = true; + bool _canRetry = true; + bool _waiting = false; + Timer? _timer; + @override + void initState() { + super.initState(); + unawaited(_load()); + } + + Future _load() async { + setState(() { + _busy = true; + _failure = null; + _waiting = false; + }); + _timer = Timer(const Duration(seconds: 10), () { + if (mounted && _busy) setState(() => _waiting = true); + }); + try { + await widget.prepare(); + if (!mounted) return; + _canRetry = false; + final app = widget.ready(); + setState(() => _ready = app); + } catch (error) { + if (mounted) setState(() => _failure = error); + } finally { + _timer?.cancel(); + if (mounted) setState(() => _busy = false); + } + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_ready != null) return _ready!; + return MaterialApp( + theme: themeData, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Builder( + builder: (context) { + final failure = _failure; + if (failure is LocalResetRecoveryRequired) { + return LocalResetProgressScreen( + initialResult: failure.result, + operation: widget.retryReset, + ); + } + final l10n = AppLocalizations.of(context)!; + return Scaffold( + body: SafeArea( + child: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (_busy) const CircularProgressIndicator(), + const SizedBox(height: 20), + Text( + _busy + ? (_waiting + ? l10n.startupWaitingBody + : l10n.startupLoadingBody) + : l10n.startupRecoveryTitle, + textAlign: TextAlign.center, + ), + if (!_busy) ...[ + const SizedBox(height: 12), + Text( + _canRetry + ? l10n.startupRecoveryBody + : l10n.restartRequiredBody, + textAlign: TextAlign.center, + ), + if (_canRetry) + FilledButton( + onPressed: _load, + child: Text(l10n.startupRetryAction), + ), + ], + ], + ), + ), + ), + ), + ); + }, + ), + ); + } +} + +/// Invalidating the installation disposes the old app's blocs/timers and never +/// rebuilds them against a closed database. Only a new process starts fresh. +class ResetAwareApp extends StatelessWidget { + const ResetAwareApp({ + super.key, + required this.gate, + required this.reset, + required this.child, + }); + final LocalDataOperationGate gate; + final Future Function() reset; + final Widget child; + @override + Widget build(BuildContext context) => AnimatedBuilder( + animation: gate, + builder: (context, _) => gate.isInvalidated + ? MaterialApp( + theme: themeData, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: LocalResetProgressScreen(operation: reset), + ) + : child, + ); +} diff --git a/test/core/database/alarm_journal_recovery_test.dart b/test/core/database/alarm_journal_recovery_test.dart new file mode 100644 index 00000000..7a11bc38 --- /dev/null +++ b/test/core/database/alarm_journal_recovery_test.dart @@ -0,0 +1,635 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:on_time_front/data/data_sources/alarm_registry_local_data_source.dart'; +import 'package:on_time_front/data/repositories/alarm_registry_repository_impl.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/core/services/alarm_journal_store_native.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; +import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/domain/entities/delivery_observation.dart'; + +import '../../domain/use-cases/reconcile_alarms_use_case_test.dart' as fixtures; +import '../services/alarm_ownership_journal_test.dart' show record; +import 'local_reset_protocol_test.dart' show FakeActions, VerifiedActions; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late Directory directory; + late File privateData; + setUp(() async { + SharedPreferences.setMockInitialValues({}); + directory = await Directory.systemTemp.createTemp( + 'ontime-corrupt-recovery-', + ); + privateData = File('${directory.path}/user-database-sentinel'); + await privateData.writeAsString('USER DATA AND KEY ARE PRESERVED'); + }); + tearDown(() async => directory.delete(recursive: true)); + + RecoveryRig reopen({ + Future Function(String)? checkpoint, + bool ios = false, + AlarmJournalStore? store, + FakeActions? actions, + }) { + final rig = RecoveryRig( + AlarmOwnershipJournal( + store ?? + FileAlarmJournalStore( + () async => directory, + checkpoint: checkpoint, + ), + ), + actions ?? FakeActions(), + ios: ios, + ); + addTearDown(rig.owner.dispose); + return rig; + } + + for (final suffix in ['', '.pending']) { + test( + 'partial JSON in $suffix recovers fresh owner without resetting user data', + () async { + final file = File('${directory.path}/ownership-v1.json$suffix'); + await file.writeAsString('{"version":1,"ownership":['); + final original = await file.readAsBytes(); + final rig = reopen(); + rig.registry.records = [record('known', 7)]; + rig.fallback.pendingFallback['orphan'] = const PendingDelivery( + id: '8', + scheduleId: 'orphan', + ); + final result = await rig.run(begin: false); + expect(result.recoveryRequired, false); + expect(result.intentRecorded, false); + expect(result.isComplete, false); + expect(rig.actions.performed, isEmpty); + expect(rig.actions.marker, false); + expect( + await privateData.readAsString(), + 'USER DATA AND KEY ARE PRESERVED', + ); + final rebuilt = await reopen().journal.read(); + expect(rebuilt.reset, ResetPhase.none); + expect( + rebuilt.unknownOwnership, + true, + ); // Android cache != native absence. + expect(rebuilt.ownership.keys.toSet(), { + 'localNotification:7', + 'localNotification:8', + }); + expect(rig.fallback.canceledFallback, isEmpty); + expect(rig.scheduler.canceledNative, isEmpty); + expect(rig.fallback.scheduledFallback, isEmpty); + expect(rig.scheduler.scheduledNative, isEmpty); + final quarantine = File( + '${directory.path}/ownership-v1.json.corrupt.0', + ); + expect(await quarantine.readAsBytes(), original); + final raw = await File( + '${directory.path}/ownership-v1.json', + ).readAsString(); + expect(raw, isNot(contains('PRIVATE'))); + expect(raw, isNot(contains('2030'))); + // Later startup sees reconstructed ownership instead of repair looping. + expect((await reopen().run(begin: false)).recoveryRequired, false); + }, + ); + } + + test( + 'only complete iOS OS observations resolve reconstructed uncertainty', + () async { + await File( + '${directory.path}/ownership-v1.json', + ).writeAsString('{broken'); + final rig = reopen(ios: true); + rig.registry.records = [record('known', 7)]; + rig.scheduler.pendingNative.add('known'); + rig.fallback.pendingFallback['orphan'] = const PendingDelivery( + id: '8', + scheduleId: 'orphan', + ); + expect((await rig.run(begin: false)).recoveryRequired, false); + final rebuilt = await rig.journal.read(); + expect(rebuilt.unknownOwnership, false); + expect(rebuilt.ownership.keys.toSet(), { + 'localNotification:7', + 'localNotification:8', + 'iosAlarmKit:known', + }); + expect(rig.actions.performed, isEmpty); + }, + ); + + for (final uncertainty in [ + 'nativeUnmapped', + 'nativeError', + 'fallbackError', + 'unownedNotification', + ]) { + test( + 'iOS $uncertainty remains partial without guessing ownership', + () async { + await File( + '${directory.path}/ownership-v1.json', + ).writeAsString('{broken'); + final rig = reopen(ios: true); + switch (uncertainty) { + case 'nativeUnmapped': + rig.scheduler.unmappedNativeCount = 1; + case 'nativeError': + rig.scheduler.nativeObservationFails = true; + case 'fallbackError': + rig.fallback.observationFails = true; + case 'unownedNotification': + rig.fallback.pendingFallback['unknown'] = const PendingDelivery( + id: '9', + ); + } + expect((await rig.run(begin: false)).recoveryRequired, false); + expect((await rig.journal.read()).unknownOwnership, true); + expect((await rig.journal.read()).ownership, isEmpty); + expect(rig.actions.performed, isEmpty); + }, + ); + } + + test( + 'secure reset marker resumes deletion but Android uncertainty remains', + () async { + await File( + '${directory.path}/ownership-v1.json', + ).writeAsString('{broken'); + final actions = FakeActions()..marker = true; + final rig = reopen(actions: actions); + final result = await rig.run(begin: false); + expect(result.intentRecorded, true); + expect(result.dataDeleted, true); + expect(result.isComplete, false); + expect((await rig.journal.read()).reset, ResetPhase.pending); + expect((await rig.journal.read()).unknownOwnership, true); + expect(actions.marker, true); + }, + ); + + test( + 'explicit reset after damage cleans quarantine only after complete receipt', + () async { + await File( + '${directory.path}/ownership-v1.json.pending', + ).writeAsString('{broken'); + final actions = VerifiedActions(); + final rig = reopen(actions: actions); + final result = await rig.run(begin: true); + expect(result.isComplete, true); + expect(actions.marker, false); + final files = await directory.list().map((entry) => entry.path).toList(); + expect(files, [ + privateData.path, + ]); // Fixture action never deletes user files. + expect((await reopen().run(begin: false)).intentRecorded, false); + }, + ); + + for (final raw in [ + '{"version":2}', + '{"version":1,"reset":"complete","completed":[],"ownership":[]}', + jsonEncode({ + 'version': 1, + 'reset': 'none', + 'completed': [], + 'ownership': [ + { + 'provider': 'localNotification', + 'scheduleId': 'a', + 'id': 1, + 'pending': true, + }, + { + 'provider': 'localNotification', + 'scheduleId': 'b', + 'id': 1, + 'pending': true, + }, + ], + }), + ]) { + test( + 'future version or impossible supported state is never automatically replaced: $raw', + () async { + final file = File('${directory.path}/ownership-v1.json'); + await file.writeAsString(raw); + final rig = reopen(); + expect((await rig.run(begin: true)).recoveryRequired, true); + expect(await file.readAsString(), raw); + expect(rig.actions.performed, isEmpty); + expect(rig.actions.marker, false); + expect(await File('${file.path}.corrupt.0').exists(), false); + expect(await File('${file.path}.recovering').exists(), false); + }, + ); + } + + test('read I/O failure does not quarantine or authorize deletion', () async { + final file = File('${directory.path}/ownership-v1.json'); + await file.writeAsString('{broken'); + final rig = reopen( + store: FileAlarmJournalStore(() async { + throw const FileSystemException('temporarily unavailable'); + }), + ); + expect((await rig.run(begin: true)).recoveryRequired, true); + expect(rig.actions.performed, isEmpty); + expect(await file.readAsString(), '{broken'); + expect(await File('${file.path}.corrupt.0').exists(), false); + }); + + for (final point in [ + 'recoveryMarked', + 'quarantinedMain', + 'written', + 'verified', + 'renamed', + 'readBack', + 'recoveryVerified', + 'recoveryPublished', + ]) { + test( + 'reconstruction interrupted at $point recovers through a new owner', + () async { + final file = File('${directory.path}/ownership-v1.json'); + await file.writeAsString('{damaged'); + final first = reopen( + checkpoint: (stage) async { + if (stage == point) throw StateError('process stopped'); + }, + ); + first.registry.records = [record('known', 7)]; + final failed = await first.run(begin: false); + expect(failed.recoveryRequired, true); + expect(first.actions.performed, isEmpty); + final next = reopen(); + next.registry.records = [record('known', 7)]; + expect((await next.run(begin: false)).recoveryRequired, false); + expect((await next.journal.read()).ownership.keys, [ + 'localNotification:7', + ]); + expect((await next.journal.read()).unknownOwnership, true); + expect(next.actions.performed, isEmpty); + expect( + await privateData.readAsString(), + 'USER DATA AND KEY ARE PRESERVED', + ); + }, + ); + } + + test( + 'first pending quarantine crash resumes without deleting user data', + () async { + final staged = File('${directory.path}/ownership-v1.json.pending'); + await staged.writeAsString('{partial first generation'); + final first = reopen( + checkpoint: (stage) async { + if (stage == 'quarantinedPending') { + throw StateError('process stopped'); + } + }, + ); + expect((await first.run(begin: false)).recoveryRequired, true); + expect(await staged.exists(), false); + expect( + await File('${directory.path}/ownership-v1.json.recovering').exists(), + true, + ); + final next = reopen(); + expect((await next.run(begin: false)).recoveryRequired, false); + expect((await next.journal.read()).unknownOwnership, true); + expect(next.actions.performed, isEmpty); + expect( + await privateData.readAsString(), + 'USER DATA AND KEY ARE PRESERVED', + ); + }, + ); + + test( + 'unverified reconstructed reset journal cannot start deletion or OS cancellation', + () async { + await File( + '${directory.path}/ownership-v1.json', + ).writeAsString('{damaged'); + final first = reopen( + checkpoint: (stage) async { + if (stage == 'recoveryVerified') { + throw StateError('lost verification'); + } + }, + ); + first.registry.records = [record('owned', 8)]; + final result = await first.run(begin: true); + expect(result.recoveryRequired, true); + expect(result.isComplete, false); + expect(first.actions.performed, isEmpty); + expect(first.actions.marker, false); + expect(first.fallback.canceledFallback, isEmpty); + expect(first.scheduler.canceledNative, isEmpty); + // Secure intent was not published, so damage-recovery cannot infer it from + // a candidate file while the recovery transaction itself is incomplete. + final next = reopen(); + next.registry.records = [record('owned', 8)]; + expect((await next.run(begin: false)).intentRecorded, false); + expect(next.actions.performed, isEmpty); + expect((await next.journal.read()).reset, ResetPhase.none); + }, + ); + + for (final ios in [true, false]) { + test( + 'real corrupt registry marker is resolved only with complete iOS evidence: $ios', + () async { + SharedPreferences.setMockInitialValues({ + 'scheduled_alarm_registry': '{PRIVATE corruption', + 'unrelated_user_preference': 'preserve', + }); + final main = File('${directory.path}/ownership-v1.json'); + await main.writeAsString('{broken'); + final rig = reopen(ios: ios); + final source = AlarmRegistryLocalDataSourceImpl(); + final registry = AlarmRegistryRepositoryImpl(localDataSource: source); + final cleanup = AlarmRegistrationCleanup( + registry, + rig.scheduler, + rig.fallback, + rig.owner, + ); + final result = await rig.owner.cleanup( + () => LocalResetProtocol( + rig.journal, + cleanup, + rig.actions, + ).run(begin: false), + ); + expect(result.recoveryRequired, false); + expect(await source.hasUnresolvedOwnership(), !ios); + await rig.owner.loadRecords(registry); + expect((await rig.journal.read()).unknownOwnership, !ios); + expect( + (await SharedPreferences.getInstance()).getString( + 'unrelated_user_preference', + ), + 'preserve', + ); + expect(rig.actions.performed, isEmpty); + }, + ); + } + + test( + 'legacy marker clear failure stays closed and fresh owner retries after journal verification', + () async { + SharedPreferences.setMockInitialValues({ + 'scheduled_alarm_registry': '{PRIVATE', + }); + final main = File('${directory.path}/ownership-v1.json'); + await main.writeAsString('{broken'); + final first = reopen(ios: true); + final source = FailingIntegritySource(first.journal); + final registry = AlarmRegistryRepositoryImpl(localDataSource: source); + final cleanup = AlarmRegistrationCleanup( + registry, + first.scheduler, + first.fallback, + first.owner, + ); + final result = await first.owner.cleanup( + () => LocalResetProtocol( + first.journal, + cleanup, + first.actions, + ).run(begin: false), + ); + expect(result.recoveryRequired, true); + expect(source.sawVerifiedJournal, true); + expect(await source.hasUnresolvedOwnership(), true); + expect((await first.journal.read()).unknownOwnership, true); + expect(first.actions.performed, isEmpty); + final next = reopen(ios: true); + final nextRegistry = AlarmRegistryRepositoryImpl( + localDataSource: AlarmRegistryLocalDataSourceImpl(), + ); + final nextCleanup = AlarmRegistrationCleanup( + nextRegistry, + next.scheduler, + next.fallback, + next.owner, + ); + expect( + (await next.owner.cleanup( + () => LocalResetProtocol( + next.journal, + nextCleanup, + next.actions, + ).run(begin: false), + )).recoveryRequired, + false, + ); + await next.owner.loadRecords(nextRegistry); + expect(await nextRegistry.hasUnresolvedOwnership(), false); + expect((await next.journal.read()).unknownOwnership, false); + expect(next.actions.performed, isEmpty); + }, + ); + + test( + 'journal-only uncertainty from temporary iOS observation failure converges on fresh startup', + () async { + final main = File('${directory.path}/ownership-v1.json'); + await main.writeAsString('{broken'); + final first = reopen(ios: true); + first.fallback.observationFails = true; + final registry = AlarmRegistryRepositoryImpl( + localDataSource: AlarmRegistryLocalDataSourceImpl(), + ); + final cleanup = AlarmRegistrationCleanup( + registry, + first.scheduler, + first.fallback, + first.owner, + ); + expect( + (await first.owner.cleanup( + () => LocalResetProtocol( + first.journal, + cleanup, + first.actions, + ).run(begin: false), + )).recoveryRequired, + false, + ); + expect((await first.journal.read()).unknownOwnership, true); + expect(await registry.hasUnresolvedOwnership(), false); + final next = reopen(ios: true); + final nextRegistry = AlarmRegistryRepositoryImpl( + localDataSource: AlarmRegistryLocalDataSourceImpl(), + ); + final nextCleanup = AlarmRegistrationCleanup( + nextRegistry, + next.scheduler, + next.fallback, + next.owner, + ); + expect( + (await next.owner.cleanup( + () => LocalResetProtocol( + next.journal, + nextCleanup, + next.actions, + ).run(begin: false), + )).recoveryRequired, + false, + ); + expect((await next.journal.read()).unknownOwnership, false); + expect((await next.journal.read()).reset, ResetPhase.none); + expect(next.actions.performed, isEmpty); + expect( + await privateData.readAsString(), + 'USER DATA AND KEY ARE PRESERVED', + ); + }, + ); + + test( + 'notification unmappedCount keeps uncertainty even with an empty entry list', + () async { + final rig = reopen(ios: true); + final cleanup = AlarmRegistrationCleanup( + rig.registry, + rig.scheduler, + UnmappedFallback(), + rig.owner, + ); + final rebuilt = await rig.owner.cleanup(cleanup.reconstructOwnership); + expect(rebuilt.unknownOwnership, true); + expect(rebuilt.ownership, isEmpty); + }, + ); + + test('invalid UTF-8 is syntax corruption, not an empty journal', () async { + final main = File('${directory.path}/ownership-v1.json'); + await main.writeAsBytes([0xff, 0xfe, 0x7b]); + final rig = reopen(); + expect((await rig.run(begin: false)).recoveryRequired, false); + expect((await rig.journal.read()).unknownOwnership, true); + expect(await File('${main.path}.corrupt.0').readAsBytes(), [ + 0xff, + 0xfe, + 0x7b, + ]); + expect(rig.actions.performed, isEmpty); + }); + + test('quarantine alone cannot be mistaken for a new installation', () async { + await File( + '${directory.path}/ownership-v1.json.corrupt.0', + ).writeAsString('{damaged'); + final rig = reopen(); + await expectLater(rig.journal.read(), throwsA(isA())); + expect((await rig.run(begin: false)).recoveryRequired, false); + expect((await rig.journal.read()).unknownOwnership, true); + expect(rig.actions.performed, isEmpty); + }); + + test( + 'future staged version is not overwritten beside a valid committed journal', + () async { + final rig = reopen(); + await rig.journal.remember([record('existing', 1)]); + final main = File('${directory.path}/ownership-v1.json'); + final before = await main.readAsBytes(); + final staged = File('${main.path}.pending'); + await staged.writeAsString('{"version":2}'); + await expectLater( + rig.journal.remember([record('new', 2)]), + throwsA(isA()), + ); + expect(await main.readAsBytes(), before); + expect(await staged.readAsString(), '{"version":2}'); + }, + ); + + test( + 'recovery refuses a future staged format even beside damaged main', + () async { + final main = File('${directory.path}/ownership-v1.json'); + final staged = File('${main.path}.pending'); + await main.writeAsString('{damaged'); + await staged.writeAsString('{"version":2}'); + final rig = reopen(); + expect((await rig.run(begin: false)).recoveryRequired, true); + expect(await main.readAsString(), '{damaged'); + expect(await staged.readAsString(), '{"version":2}'); + expect(await File('${main.path}.recovering').exists(), false); + }, + ); +} + +class RecoveryRig { + RecoveryRig(this.journal, this.actions, {required bool ios}) { + owner = AlarmOperationCoordinator( + LocalDataOperationGate(), + journal: journal, + ); + fallback.timingPermission = ios + ? AlarmPermissionState.unsupported + : AlarmPermissionState.granted; + cleanup = AlarmRegistrationCleanup(registry, scheduler, fallback, owner); + } + final AlarmOwnershipJournal journal; + final FakeActions actions; + final registry = fixtures.FakeAlarmRegistryRepository(); + final fallback = fixtures.FakeFallbackAlarmNotificationService(); + final scheduler = fixtures.FakeAlarmSchedulerService(); + late final AlarmOperationCoordinator owner; + late final AlarmRegistrationCleanup cleanup; + Future run({required bool begin}) => owner.cleanup( + () => LocalResetProtocol( + journal, + cleanup, + actions, + onIntent: owner.gate.invalidate, + ).run(begin: begin), + ); +} + +class FailingIntegritySource extends AlarmRegistryLocalDataSourceImpl { + FailingIntegritySource(this.journal); + final AlarmOwnershipJournal journal; + bool sawVerifiedJournal = false; + @override + Future clearResolvedOwnership() async { + final state = await journal.read(); + sawVerifiedJournal = + !state.unknownOwnership && state.reset == ResetPhase.none; + throw StateError('marker removal failed'); + } +} + +class UnmappedFallback extends fixtures.FakeFallbackAlarmNotificationService { + @override + Future observePending() async => + const DeliveryObservation( + source: DeliveryObservationSource.iosNotificationCenter, + unmappedCount: 1, + entries: [], + ); +} diff --git a/test/core/database/alarm_replacement_integration_test.dart b/test/core/database/alarm_replacement_integration_test.dart index 9b1d8cd0..f58ae64e 100644 --- a/test/core/database/alarm_replacement_integration_test.dart +++ b/test/core/database/alarm_replacement_integration_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/core/database/local_reset_actions.dart'; import 'package:drift/drift.dart' show Value; import 'package:drift/native.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -6,7 +7,6 @@ import 'package:on_time_front/core/backup/backup_crypto.dart'; import 'package:on_time_front/core/backup/backup_service.dart'; import 'package:on_time_front/core/database/database.dart'; import 'package:on_time_front/core/database/installation_key_store.dart'; -import 'package:on_time_front/core/database/local_data_operation_gate.dart'; import 'package:on_time_front/core/database/local_data_reset_service.dart'; import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:on_time_front/core/services/app_metadata_service.dart'; @@ -124,7 +124,7 @@ void main() { ); test( - 'durable reset intent blocks writers after cancellation failure without deleting data', + 'reset retains independent cancellation ownership after content deletion and retries only cleanup', () async { final native = concurrency.BlockingNative()..throwOnCancelIds.add('old'); final r = concurrency.Rig(native: native); @@ -135,6 +135,15 @@ void main() { keys, r.cancelAll, operationGate: r.gate, + resetActions: DeviceLocalResetActions( + keyStore: keys, + closeDatabase: database.close, + deleteFiles: () async {}, // In-memory Drift fixture owns no DB files. + clearDeliveries: + () async {}, // Provider cancellation is exercised by r. + clearLaunch: () async {}, + clearNativeDeliveries: () async => false, + ), ); r.repository.schedules = [r.schedule('old')]; final invalidated = expectLater( @@ -143,10 +152,6 @@ void main() { ); await native.entered.future; final pending = service.reset(); - final failed = expectLater( - pending, - throwsA(isA()), - ); await invalidated; await pumpEventQueue(); expect(keys.deleted, false); @@ -155,12 +160,14 @@ void main() { 'backup value', ); native.release.complete(); - await failed; + final result = await pending; + expect(result.isComplete, false); + expect(result.dataDeleted, true); expect(r.gate.isInvalidated, true); - expect(keys.deleted, false); + expect(keys.deleted, true); expect( (await SharedPreferences.getInstance()).getString('sentinel'), - 'keep', + null, ); expect( await const FlutterSecureStorage().read( @@ -173,9 +180,17 @@ void main() { r.reconcile(), throwsA(isA()), ); - await expectLater(service.reset(), throwsA(isA())); - // Existing policy: restart resumes the pending reset; no ordinary retry or - // registry wipe can silently reopen this installation in the same process. + expect((await r.operations.journal.read()).ownership, hasLength(1)); + native.throwOnCancelIds.clear(); + final completed = await service.reset(); + expect(completed.isComplete, true); + // A late progress-screen mount must not start a second destructive reset. + await (await SharedPreferences.getInstance()).setString('post-reset-sentinel', 'keep'); + expect(await service.reset(), same(completed)); + expect((await SharedPreferences.getInstance()).getString('post-reset-sentinel'), 'keep'); + expect((await r.operations.journal.read()).ownership, isEmpty); + // Even after cleanup succeeds, the old closed DB is not reopened for editing. + expect(r.gate.isInvalidated, true); }, ); } diff --git a/test/core/database/bootstrap_privacy_boundary_test.dart b/test/core/database/bootstrap_privacy_boundary_test.dart index cf0d49d0..79c90e97 100644 --- a/test/core/database/bootstrap_privacy_boundary_test.dart +++ b/test/core/database/bootstrap_privacy_boundary_test.dart @@ -1,7 +1,30 @@ +import 'package:on_time_front/core/database/local_data_lifecycle.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/core/database/bootstrap_privacy_boundary.dart'; void main() { + test( + 'pending reset preserves its ownership and bypasses unrelated privacy cleanup', + () async { + const failure = LocalResetRecoveryRequired( + LocalResetResult(intentRecorded: true, completed: {}), + ); + var cleaned = false; + await expectLater( + bootstrapWithPrivacyCleanup( + bootstrap: () async => throw failure, + cleanup: () async { + cleaned = true; + }, + shouldCleanup: (error) => error is! LocalResetRecoveryRequired, + ), + throwsA(same(failure)), + ); + expect(cleaned, false); + }, + ); + test('successful bootstrap does not remove runtime', () async { var cleaned = false; await bootstrapWithPrivacyCleanup( diff --git a/test/core/database/local_reset_actions_test.dart b/test/core/database/local_reset_actions_test.dart new file mode 100644 index 00000000..4d44dd0f --- /dev/null +++ b/test/core/database/local_reset_actions_test.dart @@ -0,0 +1,171 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/installation_key_store.dart'; +import 'package:on_time_front/core/database/local_reset_actions.dart'; +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + setUp(() { + SharedPreferences.setMockInitialValues({'private': 'fixture'}); + FlutterSecureStorage.setMockInitialValues({}); + }); + + test('marker absence is read back after deletion', () async { + final storage = _IgnoredDeletes(); + final actions = DeviceLocalResetActions(storage: storage); + await actions.writeMarker(); + expect(await actions.hasMarker(), true); + await expectLater( + actions.removeMarker(), + throwsA(isA()), + ); + expect(await actions.hasMarker(), true); + }); + + test( + 'an acknowledged but ignored intent write never authorizes deletion', + () async { + final actions = DeviceLocalResetActions(storage: _IgnoredWrites()); + await expectLater( + actions.writeMarker(), + throwsA(isA()), + ); + expect(await actions.hasMarker(), false); + }, + ); + + test( + 'key deletion checks absence without creating another installation key', + () async { + final storage = _IgnoredDeletes(); + final keys = InstallationKeyStore(storage: storage); + final original = await keys.getOrCreate(); + final actions = DeviceLocalResetActions(keyStore: keys); + await expectLater( + actions.perform(ResetStep.key), + throwsA(isA()), + ); + expect(await keys.getOrCreate(), original); + final working = InstallationKeyStore(); + await DeviceLocalResetActions(keyStore: working).perform(ResetStep.key); + expect(await working.exists(), false); + }, + ); + + test( + 'credential deletion does not accept a successful call with remaining data', + () async { + const normal = FlutterSecureStorage(); + await normal.write(key: 'accessToken', value: 'fixture'); + final actions = DeviceLocalResetActions(storage: _IgnoredDeletes()); + await expectLater( + actions.perform(ResetStep.credentials), + throwsA(isA()), + ); + await DeviceLocalResetActions().perform(ResetStep.credentials); + expect(await normal.read(key: 'accessToken'), isNull); + }, + ); + + test( + 'database is closed before file deletion and a file failure propagates', + () async { + final calls = []; + final actions = DeviceLocalResetActions( + closeDatabase: () async { + calls.add('close'); + }, + deleteFiles: () async { + calls.add('delete'); + throw StateError('fixture'); + }, + ); + await expectLater(actions.perform(ResetStep.database), throwsStateError); + expect(calls, ['close', 'delete']); + }, + ); + + test('preferences stage verifies empty storage', () async { + await DeviceLocalResetActions().perform(ResetStep.preferences); + expect((await SharedPreferences.getInstance()).getKeys(), isEmpty); + }); + + test( + 'global cleanup receipt requires both native and notification completion', + () async { + var notificationsFail = true; + final actions = DeviceLocalResetActions( + clearNativeDeliveries: () async => true, + clearDeliveries: () async { + if (notificationsFail) throw StateError('fixture'); + }, + ); + await expectLater( + actions.perform(ResetStep.deliveries), + throwsStateError, + ); + expect(actions.allProvidersConfirmedEmpty, false); + notificationsFail = false; + await actions.perform(ResetStep.deliveries); + expect(actions.allProvidersConfirmedEmpty, true); + final android = DeviceLocalResetActions( + clearNativeDeliveries: () async => false, + clearDeliveries: () async {}, + ); + await android.perform(ResetStep.deliveries); + expect(android.allProvidersConfirmedEmpty, false); + }, + ); + + test( + 'native launch cleanup is required and platform errors propagate', + () async { + const channel = MethodChannel('on_time_front/native_alarm'); + final methods = []; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (call) async { + methods.add(call.method); + throw PlatformException(code: 'cleanupUnconfirmed'); + }); + addTearDown( + () => TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null), + ); + await expectLater( + DeviceLocalResetActions().perform(ResetStep.launch), + throwsA(isA()), + ); + expect(methods, ['clearStoredLaunchPayload']); + }, + ); +} + +class _IgnoredDeletes extends FlutterSecureStorage { + @override + Future delete({ + required String key, + AppleOptions? iOptions, + AndroidOptions? aOptions, + LinuxOptions? lOptions, + WebOptions? webOptions, + AppleOptions? mOptions, + WindowsOptions? wOptions, + }) async {} +} + +class _IgnoredWrites extends FlutterSecureStorage { + @override + Future write({ + required String key, + required String? value, + AppleOptions? iOptions, + AndroidOptions? aOptions, + LinuxOptions? lOptions, + WebOptions? webOptions, + AppleOptions? mOptions, + WindowsOptions? wOptions, + }) async {} +} diff --git a/test/core/database/local_reset_protocol_test.dart b/test/core/database/local_reset_protocol_test.dart new file mode 100644 index 00000000..97cd54b6 --- /dev/null +++ b/test/core/database/local_reset_protocol_test.dart @@ -0,0 +1,314 @@ +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:on_time_front/data/data_sources/alarm_registry_local_data_source.dart'; +import 'package:on_time_front/data/repositories/alarm_registry_repository_impl.dart'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/core/services/alarm_journal_store_native.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; + +import '../../domain/use-cases/reconcile_alarms_use_case_test.dart' as fixtures; +import '../services/alarm_ownership_journal_test.dart' show record; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late Directory directory; + late FakeActions actions; + setUp(() async { + directory = await Directory.systemTemp.createTemp('ontime-reset-protocol-'); + actions = FakeActions(); + }); + tearDown(() async => directory.delete(recursive: true)); + + Rig reopen({AlarmJournalStore? store}) { + final rig = Rig( + AlarmOwnershipJournal( + store ?? FileAlarmJournalStore(() async => directory), + ), + actions, + ); + addTearDown(rig.owner.dispose); + return rig; + } + + test('legacy reset marker with no ownership is never an empty success', () async { + actions.marker = true; + final rig = reopen(); + final pending = await rig.run(begin: false); + expect(pending.intentRecorded, true); + expect(pending.dataDeleted, true); + expect(pending.isComplete, false); + expect(pending.completed, isNot(contains(ResetStep.deliveries))); + expect((await rig.journal.read()).unknownOwnership, true); + expect((await reopen().run(begin: false)).isComplete, false); + final verified = VerifiedActions()..marker = true; + final restarted = reopen(); + final complete = await restarted.owner.cleanup(() => LocalResetProtocol( + restarted.journal, restarted.cleanup, verified).run(begin: false)); + expect(complete.isComplete, true); + }); + + test( + 'corrupt registry uncertainty survives preference deletion and restart', + () async { + SharedPreferences.setMockInitialValues({ + 'scheduled_alarm_registry': 'PRIVATE corrupt', + }); + final rig = reopen(); + final registry = AlarmRegistryRepositoryImpl( + localDataSource: AlarmRegistryLocalDataSourceImpl(), + ); + final cleanup = AlarmRegistrationCleanup( + registry, + fixtures.FakeAlarmSchedulerService(), + rig.fallback, + rig.owner, + ); + final first = await rig.owner.cleanup( + () => + LocalResetProtocol(rig.journal, cleanup, actions).run(begin: true), + ); + expect(first.dataDeleted, true); + expect(first.isComplete, false); + expect((await rig.journal.read()).unknownOwnership, true); + await (await SharedPreferences.getInstance()).clear(); + final restarted = reopen(); + final retry = await restarted.run(begin: false); + expect(retry.isComplete, false); + expect(retry.completed, isNot(contains(ResetStep.deliveries))); + expect((await restarted.journal.read()).unknownOwnership, true); + expect( + await File('${directory.path}/ownership-v1.json').readAsString(), + isNot(contains('PRIVATE')), + ); + }, + ); + + test( + 'unknown ownership clears only after an app-wide provider receipt', + () async { + final rig = reopen(); + await rig.journal.save(AlarmJournalSnapshot(unknownOwnership: true)); + final verified = VerifiedActions(); + final result = await rig.owner.cleanup( + () => LocalResetProtocol( + rig.journal, + rig.cleanup, + verified, + ).run(begin: true), + ); + expect(result.isComplete, true); + expect((await rig.journal.read()).unknownOwnership, false); + }, + ); + + test( + 'ordinary ownership does not authorize reset or remove user data', + () async { + final rig = reopen(); + await rig.journal.remember([record('existing', 1)]); + final receipt = await rig.run(begin: false); + expect(receipt.intentRecorded, false); + expect(receipt.isComplete, false); + expect(actions.performed, isEmpty); + expect((await reopen().journal.read()).ownership, hasLength(1)); + }, + ); + + test( + 'returned cancel failure permits content deletion and survives fresh-instance retry', + () async { + final first = reopen(); + first.registry.records = [record('old', 1)]; + first.fallback.throwOnCancelIds.add('old'); + final pending = await first.run(begin: true); + expect(pending.intentRecorded, true); + expect(pending.isComplete, false); + expect(pending.dataDeleted, true); + expect(actions.marker, true); + expect(first.owner.gate.isInvalidated, true); + expect((await first.journal.read()).ownership.keys, [ + 'localNotification:1', + ]); + + final dataCalls = actions.performed + .where((s) => s != ResetStep.deliveries) + .toList(); + final restarted = + reopen(); // Empty preference registry, independent journal retained. + final complete = await restarted.run(begin: false); + expect(complete.isComplete, true); + expect(actions.marker, false); + expect( + restarted.fallback.canceledFallback.single.fallbackNotificationId, + 1, + ); + expect( + actions.performed.where((s) => s != ResetStep.deliveries), + dataCalls, + ); + expect((await restarted.journal.read()).reset, ResetPhase.none); + }, + ); + + for (final failedStep in ResetStep.values.where( + (s) => s != ResetStep.deliveries, + )) { + test( + 'reopen retries only unconfirmed $failedStep deletion stage', + () async { + actions.failures.add(failedStep); + final first = await reopen().run(begin: true); + expect(first.isComplete, false); + expect(first.completed, isNot(contains(failedStep))); + expect(actions.marker, true); + final countBefore = actions.performed.length; + actions.failures.clear(); + final second = await reopen().run(begin: false); + expect(second.isComplete, true); + expect(actions.performed.skip(countBefore), [failedStep]); + }, + ); + } + + test( + 'failed marker removal reopens completed journal without deleting data again', + () async { + actions.failMarkerRemoval = true; + final first = await reopen().run(begin: true); + expect(first.isComplete, false); + expect((await reopen().journal.read()).reset, ResetPhase.complete); + final calls = List.of(actions.performed); + actions.failMarkerRemoval = false; + expect((await reopen().run(begin: false)).isComplete, true); + expect(actions.performed, calls); + expect(actions.marker, false); + }, + ); + + test( + 'completed-journal unlink failure cannot replay deletion against new content', + () async { + final failing = _RemoveFailure( + FileAlarmJournalStore(() async => directory), + ); + final first = await reopen(store: failing).run(begin: true); + expect(first.isComplete, false); + expect(actions.marker, false); + expect((await reopen().journal.read()).reset, ResetPhase.complete); + final calls = List.of(actions.performed); + expect((await reopen().run(begin: false)).isComplete, true); + expect(actions.performed, calls); + expect((await reopen().run(begin: false)).intentRecorded, false); + expect(actions.performed, calls); + }, + ); + + test( + 'unverified intent write reports no completion and never deletes data', + () async { + final failing = _IntentWriteFailure( + FileAlarmJournalStore(() async => directory), + ); + final rig = reopen(store: failing); + final result = await rig.run(begin: true); + expect(result.intentRecorded, false); + expect(result.completed, isEmpty); + expect(result.dataDeleted, false); + expect(actions.performed, isEmpty); + expect(actions.marker, false); + expect(rig.owner.gate.isInvalidated, true); + // The storage error occurred after rename. A fresh process discovers the + // committed intent, rather than treating the ambiguous response as rollback. + expect((await reopen().journal.read()).reset, ResetPhase.pending); + expect((await reopen().run(begin: false)).isComplete, true); + }, + ); +} + +class Rig { + Rig(this.journal, this.actions) { + owner = AlarmOperationCoordinator( + LocalDataOperationGate(), + journal: journal, + ); + cleanup = AlarmRegistrationCleanup( + registry, + fixtures.FakeAlarmSchedulerService(), + fallback, + owner, + ); + } + final AlarmOwnershipJournal journal; + final FakeActions actions; + final registry = fixtures.FakeAlarmRegistryRepository(); + final fallback = fixtures.FakeFallbackAlarmNotificationService(); + late final AlarmOperationCoordinator owner; + late final AlarmRegistrationCleanup cleanup; + Future run({required bool begin}) => owner.cleanup( + () => LocalResetProtocol( + journal, + cleanup, + actions, + onIntent: owner.gate.invalidate, + ).run(begin: begin), + ); +} + +class FakeActions implements LocalResetActions { + bool marker = false; + bool failMarkerRemoval = false; + final failures = {}; + final performed = []; + @override + Future hasMarker() async => marker; + @override + Future writeMarker() async { + marker = true; + } + + @override + Future removeMarker() async { + if (failMarkerRemoval) throw StateError('marker unavailable'); + marker = false; + } + + @override + Future perform(ResetStep step) async { + performed.add(step); + if (failures.contains(step)) throw StateError('stage failed'); + } +} + +class _RemoveFailure implements AlarmJournalStore { + _RemoveFailure(this.inner); + final AlarmJournalStore inner; + @override + Future read() => inner.read(); + @override + Future write(String value) => inner.write(value); + @override + Future remove() async => throw StateError('unlink failed'); +} + +class _IntentWriteFailure extends _RemoveFailure { + _IntentWriteFailure(super.inner); + @override + Future write(String value) async { + await inner.write(value); + if (AlarmJournalSnapshot.decode(value).reset == ResetPhase.pending) { + throw StateError('lost acknowledgement after rename'); + } + } +} + +class VerifiedActions extends FakeActions + implements ResetGlobalDeliveryCleanup { + @override + bool get allProvidersConfirmedEmpty => + performed.contains(ResetStep.deliveries); +} diff --git a/test/core/services/alarm_ownership_journal_test.dart b/test/core/services/alarm_ownership_journal_test.dart new file mode 100644 index 00000000..93a3f172 --- /dev/null +++ b/test/core/services/alarm_ownership_journal_test.dart @@ -0,0 +1,239 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/services/alarm_journal_store_native.dart'; +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/domain/entities/alarm_entities.dart'; + +void main() { + late Directory directory; + setUp(() async { + directory = await Directory.systemTemp.createTemp('ontime-journal-test-'); + }); + tearDown(() async => directory.delete(recursive: true)); + + AlarmOwnershipJournal reopen({Future Function(String)? checkpoint}) => + AlarmOwnershipJournal( + FileAlarmJournalStore(() async => directory, checkpoint: checkpoint), + ); + + test( + 'independent file retains only cancellation identity across reopen', + () async { + final journal = reopen(); + await journal.remember([record('same', 11), record('same', 12)]); + final raw = await File( + '${directory.path}/ownership-v1.json', + ).readAsString(); + for (final secret in [ + 'PRIVATE TITLE', + 'PRIVATE PAYLOAD', + 'PRIVATE FINGERPRINT', + '2030', + ]) { + expect(raw, isNot(contains(secret))); + } + final again = await reopen().read(); + expect(again.ownership.keys.toSet(), { + 'localNotification:11', + 'localNotification:12', + }); + expect(again.ownership.values.every((entry) => entry.pending), true); + expect(again.reset, ResetPhase.none); + await reopen().remember([record('same', 11)], pending: false); + await reopen().confirmedCancelled(record('same', 11)); + expect((await reopen().read()).ownership.keys, ['localNotification:12']); + }, + ); + + for (final point in ['written', 'verified', 'renamed', 'readBack']) { + test( + 'interrupted replacement at $point reopens a complete committed generation', + () async { + await reopen().remember([record('old', 1)]); + final interrupted = reopen( + checkpoint: (stage) async { + if (stage == point) throw StateError('simulated interruption'); + }, + ); + await expectLater( + interrupted.remember([record('new', 2)]), + throwsStateError, + ); + final restored = await reopen().read(); + expect( + restored.ownership.keys.toSet(), + point == 'written' || point == 'verified' + ? {'localNotification:1'} + : {'localNotification:1', 'localNotification:2'}, + ); + // A subsequent complete write replaces any leftover staged generation. + await reopen().remember([record('retry', 3)]); + expect( + (await reopen().read()).ownership.containsKey('localNotification:3'), + true, + ); + }, + ); + } + + test( + 'staged first write is preserved and never reported as an empty installation', + () async { + final interrupted = reopen( + checkpoint: (stage) async { + if (stage == 'written') throw StateError('stop before commit'); + }, + ); + await expectLater( + interrupted.remember([record('first', 1)]), + throwsStateError, + ); + final staged = File('${directory.path}/ownership-v1.json.pending'); + final bytes = await staged.readAsBytes(); + final recovered = await reopen().read(); + expect(recovered.ownership.keys, ['localNotification:1']); + expect(recovered.ownership.values.single.pending, true); + expect(await staged.exists(), false); + expect( + await File('${directory.path}/ownership-v1.json').readAsBytes(), + bytes, + ); + }, + ); + + test( + 'truncated staged first write is preserved and blocks new ownership', + () async { + final staged = File('${directory.path}/ownership-v1.json.pending'); + await staged.writeAsString('{truncated'); + await expectLater( + reopen().read(), + throwsA(isA()), + ); + await expectLater( + reopen().remember([record('new', 2)]), + throwsA(isA()), + ); + expect(await staged.readAsString(), '{truncated'); + }, + ); + + for (final raw in [ + '{broken', + '{"version":2}', + '{"version":1,"ownership":[]}', + ]) { + test( + 'malformed or unsupported committed journal stays intact: $raw', + () async { + final file = File('${directory.path}/ownership-v1.json'); + await file.writeAsString(raw); + await expectLater( + reopen().read(), + throwsA(isA()), + ); + await expectLater( + reopen().remember([record('new', 2)]), + throwsA(isA()), + ); + expect(await file.readAsString(), raw); + }, + ); + } + + test('backup exclusion failure prevents creating ownership bytes', () async { + final store = FileAlarmJournalStore( + () async => directory, + excludeFromBackup: (_) async => throw StateError('exclusion failed'), + ); + await expectLater( + AlarmOwnershipJournal(store).remember([record('a', 1)]), + throwsStateError, + ); + expect(await directory.list().toList(), isEmpty); + }); + + test('only a fully completed reset journal can be removed', () async { + await reopen().remember([record('still-active', 1)]); + await expectLater( + reopen().removeCompleted(), + throwsA(isA()), + ); + expect((await reopen().read()).ownership, isNotEmpty); + await reopen().confirmedCancelled(record('still-active', 1)); + await reopen().save( + AlarmJournalSnapshot( + reset: ResetPhase.complete, + completed: ResetStep.values.toSet(), + ), + ); + await reopen().removeCompleted(); + expect(await directory.list().toList(), isEmpty); + }); + + test( + 'duplicate actual platform IDs are rejected instead of hiding ownership', + () { + final entry = AlarmOwnership(record('same', 1), pending: true).toJson(); + final raw = jsonEncode({ + 'version': 1, + 'reset': 'none', + 'completed': [], + 'ownership': [ + entry, + {...entry, 'scheduleId': 'another-label'}, + ], + }); + expect( + () => AlarmJournalSnapshot.decode(raw), + throwsA(isA()), + ); + }, + ); + + test( + 'impossible completion states cannot be written over a valid journal', + () async { + final journal = reopen(); + await journal.remember([record('active', 1)]); + final file = File('${directory.path}/ownership-v1.json'); + final original = await file.readAsBytes(); + for (final invalid in [ + AlarmJournalSnapshot(completed: {ResetStep.database}), + AlarmJournalSnapshot( + reset: ResetPhase.complete, + completed: {ResetStep.database}, + ), + AlarmJournalSnapshot( + reset: ResetPhase.pending, + completed: {ResetStep.deliveries}, + ownership: { + 'localNotification:1': AlarmOwnership( + record('active', 1), + pending: false, + ), + }, + ), + ]) { + await expectLater( + journal.save(invalid), + throwsA(isA()), + ); + expect(await file.readAsBytes(), original); + } + }, + ); +} + +ScheduledAlarmRecord record(String scheduleId, int id) => ScheduledAlarmRecord( + scheduleId: scheduleId, + provider: AlarmProvider.localNotification, + fallbackNotificationId: id, + alarmTime: DateTime.utc(2030, 1, 1), + preparationStartTime: DateTime.utc(2030, 1, 1), + scheduleFingerprint: 'PRIVATE FINGERPRINT', + scheduleTitle: 'PRIVATE TITLE', + payload: const {'secret': 'PRIVATE PAYLOAD'}, +); diff --git a/test/data/data_sources/alarm_registry_local_data_source_test.dart b/test/data/data_sources/alarm_registry_local_data_source_test.dart index 03d47b58..b20262c3 100644 --- a/test/data/data_sources/alarm_registry_local_data_source_test.dart +++ b/test/data/data_sources/alarm_registry_local_data_source_test.dart @@ -15,6 +15,40 @@ void main() { dataSource = AlarmRegistryLocalDataSourceImpl(); }); + test( + 'scrubbing irrecoverable identities retains a content-free uncertainty marker', + () async { + for (final raw in [ + 'PRIVATE corrupt', + '', + '{}', + '[null]', + '[{"scheduleId":"a","provider":"unknown"}]', + ['PRIVATE'], + ]) { + SharedPreferences.setMockInitialValues({ + 'scheduled_alarm_registry': raw, + }); + final source = AlarmRegistryLocalDataSourceImpl(); + expect(await source.loadAll(), isEmpty); + expect(await source.hasUnresolvedOwnership(), true); + final prefs = await SharedPreferences.getInstance(); + expect(prefs.get('scheduled_alarm_registry'), isNull); + expect(prefs.get('scheduled_alarm_ownership_unknown_v1'), true); + await source.replaceAll([]); + expect( + await AlarmRegistryLocalDataSourceImpl().hasUnresolvedOwnership(), + true, + ); + } + }, + ); + + test('missing registry is not mistaken for a corrupt registry', () async { + expect(await dataSource.loadAll(), isEmpty); + expect(await dataSource.hasUnresolvedOwnership(), false); + }); + test( 'wrong-typed known registry is removed instead of retaining raw text', () async { diff --git a/test/data/repositories/alarm_registry_repository_impl_test.dart b/test/data/repositories/alarm_registry_repository_impl_test.dart index d3d8eb8b..b449f1d4 100644 --- a/test/data/repositories/alarm_registry_repository_impl_test.dart +++ b/test/data/repositories/alarm_registry_repository_impl_test.dart @@ -1,3 +1,5 @@ +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import '../../helpers/isolated_alarm_owner.dart'; import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; import '../../domain/use-cases/reconcile_alarms_use_case_test.dart' as fixtures; import 'package:shared_preferences/shared_preferences.dart'; @@ -7,6 +9,10 @@ import 'package:on_time_front/data/repositories/alarm_registry_repository_impl.d import 'package:on_time_front/domain/entities/alarm_entities.dart'; void main() { + late AlarmOperationCoordinator isolatedOwner; + setUp(() { + isolatedOwner = isolatedAlarmOwner(); + }); TestWidgetsFlutterBinding.ensureInitialized(); late _FakeAlarmRegistryLocalDataSource localDataSource; late AlarmRegistryRepositoryImpl repository; @@ -104,6 +110,8 @@ void main() { nowProvider: () => now, timeZoneProvider: () async => 'UTC', languageCodeProvider: () => 'en', + + operations: isolatedOwner, ); final result = await reconcile(); if (failDuplicateCancellation) { diff --git a/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart b/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart index a9a41502..58fa4866 100644 --- a/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart +++ b/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart @@ -1,3 +1,4 @@ +import '../../helpers/isolated_alarm_owner.dart'; import 'dart:async'; import 'package:on_time_front/core/database/local_data_operation_gate.dart'; import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; @@ -39,6 +40,10 @@ class BlockingAlarmRepository extends fixtures.FakeAlarmRepository { } void main() { + late AlarmOperationCoordinator isolatedOwner; + setUp(() { + isolatedOwner = isolatedAlarmOwner(); + }); test( 'each cutoff finishes without waiting for later edits to become quiet', () async { @@ -314,6 +319,8 @@ void main() { fallback, nowProvider: () => now, timeZoneProvider: () async => 'UTC', + + operations: isolatedOwner, ); final first = reconcile(); await repository.snapshots[1]!.future; diff --git a/test/domain/use-cases/cancel_alarms_use_cases_test.dart b/test/domain/use-cases/cancel_alarms_use_cases_test.dart index 83b0ca18..99573023 100644 --- a/test/domain/use-cases/cancel_alarms_use_cases_test.dart +++ b/test/domain/use-cases/cancel_alarms_use_cases_test.dart @@ -1,3 +1,4 @@ +import '../../helpers/isolated_alarm_owner.dart'; import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/core/services/alarm_scheduler_service.dart'; @@ -8,6 +9,10 @@ import 'package:on_time_front/domain/use-cases/cancel_all_alarms_use_case.dart'; import 'package:on_time_front/domain/use-cases/cancel_schedule_alarm_use_case.dart'; void main() { + late AlarmOperationCoordinator isolatedOwner; + setUp(() { + isolatedOwner = isolatedAlarmOwner(); + }); test( 'CancelScheduleAlarmUseCase cancels matching native and fallback records', () async { @@ -19,7 +24,12 @@ void main() { ]); final scheduler = _FakeAlarmSchedulerService(); final fallback = _FakeFallbackAlarmNotificationService(); - final useCase = CancelScheduleAlarmUseCase(registry, scheduler, fallback); + final useCase = CancelScheduleAlarmUseCase( + registry, + scheduler, + fallback, + operations: isolatedOwner, + ); await useCase('schedule-1'); @@ -44,6 +54,8 @@ void main() { registry, scheduler, _FakeFallbackAlarmNotificationService(), + + operations: isolatedOwner, ); await expectLater( @@ -66,7 +78,12 @@ void main() { ]); final scheduler = _FakeAlarmSchedulerService(); final fallback = _FakeFallbackAlarmNotificationService(); - final useCase = CancelAllAlarmsUseCase(registry, scheduler, fallback); + final useCase = CancelAllAlarmsUseCase( + registry, + scheduler, + fallback, + operations: isolatedOwner, + ); await useCase(); @@ -86,6 +103,8 @@ void main() { registry, _FakeAlarmSchedulerService(), _FakeFallbackAlarmNotificationService(), + + operations: isolatedOwner, ); await useCase(); diff --git a/test/domain/use-cases/delivery_reconciliation_test.dart b/test/domain/use-cases/delivery_reconciliation_test.dart index 230617a8..12092687 100644 --- a/test/domain/use-cases/delivery_reconciliation_test.dart +++ b/test/domain/use-cases/delivery_reconciliation_test.dart @@ -1,3 +1,6 @@ +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import '../../helpers/isolated_alarm_owner.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/domain/entities/alarm_entities.dart'; import 'package:on_time_front/domain/entities/delivery_observation.dart'; @@ -12,6 +15,10 @@ import 'reconcile_alarms_use_case_test.dart' scheduleWithAlarmAt; void main() { + late AlarmOperationCoordinator isolatedOwner; + setUp(() { + isolatedOwner = isolatedAlarmOwner(); + }); final now = DateTime(2026, 5, 5, 9); late FakeAlarmRepository schedules; late FakeAlarmRegistryRepository registry; @@ -44,9 +51,30 @@ void main() { nowProvider: () => now, languageCodeProvider: () => 'en', timeZoneProvider: () async => 'UTC', + + operations: isolatedOwner, ); }); + for (final enabled in [true, false]) { + test( + 'lost ownership is not reported as armed or disabled: enabled=$enabled', + () async { + schedules.settings = AlarmSettings(alarmsEnabled: enabled); + await isolatedOwner.journal.save( + AlarmJournalSnapshot(unknownOwnership: true), + ); + final result = await reconcile(); + expect(result.status, AlarmReconciliationStatus.partial); + expect(result.armedScheduleIds, isEmpty); + expect(result.failures, isNotEmpty); + expect(native.scheduledNative, isEmpty); + expect(notifications.scheduledFallback, isEmpty); + expect((await isolatedOwner.journal.read()).unknownOwnership, true); + }, + ); + } + test( 'write-ahead identity precedes native scheduling and a now-expired target is not replayed', () async { @@ -68,6 +96,8 @@ void main() { nowProvider: () => clock, languageCodeProvider: () => 'en', timeZoneProvider: () async => 'UTC', + + operations: isolatedOwner, ); final result = await delayed(); expect(sawIntent, true); @@ -88,9 +118,11 @@ void main() { ); notifications.timingPermission = AlarmPermissionState.granted; await reconcile(); - registry.records = [ - registry.records.single.copyWith(fallbackNotificationId: 99), - ]; + final previous = registry.records.single; + registry.records = [previous.copyWith(fallbackNotificationId: 99)]; + // Seed migrated ownership consistently in both persistence projections. + await isolatedOwner.journal.confirmedCancelled(previous); + await isolatedOwner.journal.remember(registry.records, pending: false); notifications.pendingFallback ..clear() ..['99'] = const PendingDelivery(id: '99', scheduleId: 'one'); diff --git a/test/domain/use-cases/reconcile_alarms_use_case_test.dart b/test/domain/use-cases/reconcile_alarms_use_case_test.dart index 3a7b9ee4..a23c4a0e 100644 --- a/test/domain/use-cases/reconcile_alarms_use_case_test.dart +++ b/test/domain/use-cases/reconcile_alarms_use_case_test.dart @@ -1,3 +1,5 @@ +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import '../../helpers/isolated_alarm_owner.dart'; import 'package:on_time_front/domain/entities/delivery_observation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/data/models/scheduled_alarm_record_model.dart'; @@ -275,6 +277,10 @@ class FakeFallbackAlarmNotificationService } void main() { + late AlarmOperationCoordinator isolatedOwner; + setUp(() { + isolatedOwner = isolatedAlarmOwner(); + }); late DateTime now; late FakeAlarmRepository alarmRepository; late FakeAlarmRegistryRepository registryRepository; @@ -301,6 +307,8 @@ void main() { nowProvider: () => now, languageCodeProvider: () => language, timeZoneProvider: () async => deviceZone, + + operations: isolatedOwner, ); }); diff --git a/test/domain/use-cases/recurring_alarm_integration_test.dart b/test/domain/use-cases/recurring_alarm_integration_test.dart index d2332d81..bb62ff7d 100644 --- a/test/domain/use-cases/recurring_alarm_integration_test.dart +++ b/test/domain/use-cases/recurring_alarm_integration_test.dart @@ -1,3 +1,4 @@ +import '../../helpers/isolated_alarm_owner.dart'; import 'dart:async'; import 'package:on_time_front/core/database/local_data_operation_gate.dart'; import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; @@ -38,6 +39,10 @@ import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { + late AlarmOperationCoordinator isolatedOwner; + setUp(() { + isolatedOwner = isolatedAlarmOwner(); + }); TestWidgetsFlutterBinding.ensureInitialized(); late AppDatabase database; late RecurringScheduleRepositoryImpl recurring; @@ -58,6 +63,8 @@ void main() { native, _Notifications(), nowProvider: () => now, + + operations: isolatedOwner, ); Future createSeries( diff --git a/test/helpers/isolated_alarm_owner.dart b/test/helpers/isolated_alarm_owner.dart new file mode 100644 index 00000000..abd39644 --- /dev/null +++ b/test/helpers/isolated_alarm_owner.dart @@ -0,0 +1,15 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; + +/// Unit fixtures intentionally use an ephemeral journal; file persistence and +/// restart recovery are exercised separately with actual temporary files. +AlarmOperationCoordinator isolatedAlarmOwner() { + final gate = LocalDataOperationGate(); + final owner = AlarmOperationCoordinator(gate); + addTearDown(() { + owner.dispose(); + gate.dispose(); + }); + return owner; +} diff --git a/test/helpers/noop_alarm_cleanup.dart b/test/helpers/noop_alarm_cleanup.dart index 17014c1a..9085139b 100644 --- a/test/helpers/noop_alarm_cleanup.dart +++ b/test/helpers/noop_alarm_cleanup.dart @@ -1,8 +1,17 @@ import 'package:on_time_front/domain/use-cases/cancel_all_alarms_use_case.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; /// Backup-only fixtures have no platform registrations. Concurrency integration /// uses the real cleanup use case and a stateful platform fake instead. class NoopAlarmCleanup implements CancelAllAlarmsUseCase { + @override + AlarmOperationCoordinator get operations => + throw UnsupportedError('Use actual cleanup for reset protocol tests'); + @override + Future withCleanupOwner( + Future Function(AlarmRegistrationCleanup) action, + ) => throw UnsupportedError('Use actual cleanup for reset protocol tests'); @override Future call() async {} @override diff --git a/test/presentation/startup/local_reset_recovery_test.dart b/test/presentation/startup/local_reset_recovery_test.dart new file mode 100644 index 00000000..28a1858e --- /dev/null +++ b/test/presentation/startup/local_reset_recovery_test.dart @@ -0,0 +1,195 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/local_data_lifecycle.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/l10n/app_localizations.dart'; +import 'package:on_time_front/presentation/startup/screens/local_reset_progress_screen.dart'; +import 'package:on_time_front/presentation/startup/screens/local_startup_gate.dart'; + +void main() { + testWidgets( + 'pre-DI reset failure exposes retry without constructing the normal app', + (tester) async { + tester.binding.platformDispatcher.localeTestValue = const Locale('en'); + addTearDown(tester.binding.platformDispatcher.clearLocaleTestValue); + var constructed = 0; + var retried = 0; + await tester.pumpWidget( + LocalStartupGate( + prepare: () async => throw const LocalResetRecoveryRequired( + LocalResetResult(intentRecorded: true, completed: {}), + ), + ready: () { + constructed++; + return const Text('normal app'); + }, + retryReset: () async { + retried++; + return completed(); + }, + ), + ); + await tester.pump(); + expect(constructed, 0); + expect(find.text('Retry cleanup'), findsOneWidget); + await tester.tap(find.text('Retry cleanup')); + await tester.pumpAndSettle(); + expect(retried, 1); + expect(constructed, 0); + expect(find.text('Local data reset complete'), findsOneWidget); + }, + ); + + testWidgets( + 'pre-DI bootstrap failure can retry without showing private exception text', + (tester) async { + tester.binding.platformDispatcher.localeTestValue = const Locale('en'); + addTearDown(tester.binding.platformDispatcher.clearLocaleTestValue); + var attempts = 0; + await tester.pumpWidget( + LocalStartupGate( + prepare: () async { + if (++attempts == 1) throw StateError('PRIVATE PATH'); + }, + ready: () => const MaterialApp(home: Text('ready')), + retryReset: () async => completed(), + ), + ); + await tester.pump(); + expect(find.textContaining('PRIVATE PATH'), findsNothing); + expect(find.text('ready'), findsNothing); + await tester.tap(find.text('Try again')); + await tester.pumpAndSettle(); + expect(attempts, 2); + expect(find.text('ready'), findsOneWidget); + }, + ); + + testWidgets( + 'reset invalidation disposes the old app while the actual platform future remains pending', + (tester) async { + tester.binding.platformDispatcher.localeTestValue = const Locale('en'); + addTearDown(tester.binding.platformDispatcher.clearLocaleTestValue); + final gate = LocalDataOperationGate(); + addTearDown(gate.dispose); + final latePlatform = Completer(); + var calls = 0; + var disposals = 0; + await tester.pumpWidget( + ResetAwareApp( + gate: gate, + reset: () { + calls++; + return latePlatform.future; + }, + child: MaterialApp(home: DisposalProbe(onDispose: () => disposals++)), + ), + ); + gate.invalidate(); + await tester.pump(); + expect(disposals, 1); + expect(calls, 1); + await tester.pump(const Duration(seconds: 11)); + expect(find.textContaining('Waiting for the device'), findsOneWidget); + expect(find.text('Retry cleanup'), findsNothing); + expect(calls, 1); + latePlatform.complete(completed()); + await tester.pumpAndSettle(); + expect(find.text('Local data reset complete'), findsOneWidget); + expect(find.text('old app'), findsNothing); + expect(calls, 1); + }, + ); + + for (final locale in ['ko', 'en']) { + testWidgets( + '$locale partial deletion screen is distinct from complete and fits large text', + (tester) async { + tester.view.physicalSize = const Size(430, 932); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + var retries = 0; + await tester.pumpWidget( + MaterialApp( + locale: Locale(locale), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + builder: (context, child) => MediaQuery( + data: MediaQuery.of( + context, + ).copyWith(textScaler: const TextScaler.linear(2)), + child: child!, + ), + home: LocalResetProgressScreen( + initialResult: const LocalResetResult( + intentRecorded: true, + completed: { + ResetStep.database, + ResetStep.preferences, + ResetStep.key, + ResetStep.credentials, + }, + ), + operation: () async { + retries++; + return completed(); + }, + ), + ), + ); + await tester.pumpAndSettle(); + expect( + find.textContaining(locale == 'ko' ? '일부 알림' : 'some notification'), + findsOneWidget, + ); + expect( + find.text( + locale == 'ko' ? '로컬 데이터 초기화 완료' : 'Local data reset complete', + ), + findsNothing, + ); + expect(tester.takeException(), isNull); + final button = find.text(locale == 'ko' ? '정리 다시 시도' : 'Retry cleanup'); + await tester.ensureVisible(button); + await tester.tap(button); + await tester.pumpAndSettle(); + expect(retries, 1); + expect( + find.text( + locale == 'ko' ? '로컬 데이터 초기화 완료' : 'Local data reset complete', + ), + findsOneWidget, + ); + }, + ); + } +} + +LocalResetResult completed() => LocalResetResult( + intentRecorded: true, + completed: ResetStep.values.toSet(), + isComplete: true, + recoveryRequired: false, +); + +class DisposalProbe extends StatefulWidget { + const DisposalProbe({super.key, required this.onDispose}); + final VoidCallback onDispose; + @override + State createState() => _DisposalProbeState(); +} + +class _DisposalProbeState extends State { + @override + void dispose() { + widget.onDispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => const Text('old app'); +} diff --git a/tool/qa/capture_reset_recovery.dart b/tool/qa/capture_reset_recovery.dart new file mode 100644 index 00000000..75c4e08e --- /dev/null +++ b/tool/qa/capture_reset_recovery.dart @@ -0,0 +1,86 @@ +// Widget raster evidence, not a mobile OS screenshot or delivery test. +import 'dart:io'; +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/l10n/app_localizations.dart'; +import 'package:on_time_front/presentation/shared/theme/theme.dart'; +import 'package:on_time_front/presentation/startup/screens/local_reset_progress_screen.dart'; + +void main() { + testWidgets('capture localized reset receipts with bundled fonts', ( + tester, + ) async { + final output = Directory(Platform.environment['D03_CAPTURE_DIR']!); + await tester.runAsync(() => output.create(recursive: true)); + final font = FontLoader('Pretendard') + ..addFont(rootBundle.load('assets/fonts/Pretendard-Regular.ttf')); + await tester.runAsync(font.load); + final icons = FontLoader('MaterialIcons') + ..addFont(rootBundle.load('fonts/MaterialIcons-Regular.otf')); + await tester.runAsync(icons.load); + tester.view.physicalSize = const Size(430, 932); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + for (final locale in ['ko', 'en']) { + for (final complete in [false, true]) { + final result = LocalResetResult( + intentRecorded: true, + completed: complete + ? ResetStep.values.toSet() + : { + ResetStep.database, + ResetStep.preferences, + ResetStep.key, + ResetStep.credentials, + ResetStep.launch, + }, + isComplete: complete, + recoveryRequired: !complete, + ); + final boundaryKey = GlobalKey(); + await tester.pumpWidget( + MaterialApp( + key: ValueKey('$locale-$complete'), + theme: themeData, + locale: Locale(locale), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: MediaQuery( + data: const MediaQueryData( + size: Size(430, 932), + textScaler: TextScaler.linear(2), + ), + child: RepaintBoundary( + key: boundaryKey, + child: LocalResetProgressScreen( + initialResult: result, + operation: () async => result, + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + expect(tester.takeException(), isNull); + final boundary = + boundaryKey.currentContext!.findRenderObject()! + as RenderRepaintBoundary; + await tester.runAsync(() async { + final image = await boundary.toImage(pixelRatio: 2); + final bytes = await image.toByteData(format: ui.ImageByteFormat.png); + final file = File( + '${output.path}/reset-${complete ? 'complete' : 'pending'}-$locale.png', + ); + await file.writeAsBytes(bytes!.buffer.asUint8List(), flush: true); + image.dispose(); + }); + } + } + }); +} diff --git a/tool/qa/reset_journal_crash_probe.dart b/tool/qa/reset_journal_crash_probe.dart new file mode 100644 index 00000000..f9c41270 --- /dev/null +++ b/tool/qa/reset_journal_crash_probe.dart @@ -0,0 +1,184 @@ +// Run only through verify_reset_crash_recovery.py, never against app data. +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/core/services/alarm_journal_store_native.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; +import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; + +import '../../test/domain/use-cases/reconcile_alarms_use_case_test.dart' + as fixtures; +import '../../test/core/services/alarm_ownership_journal_test.dart' show record; + +void main() { + test( + 'isolated reset survives a killed host process', + () async { + final directory = Directory(Platform.environment['D03_PROBE_DIR']!); + final point = Platform.environment['D03_PROBE_POINT']!; + final recovering = Platform.environment['D03_PROBE_MODE'] == 'recover'; + final syntaxRecovery = const { + 'partial-first-pending', + 'recoveryMarked', + 'quarantinedPending', + 'recoveryVerified', + 'recoveryPublished', + }.contains(point); + final ready = File('${directory.path}/cut-ready'); + Future cut(String stage) async { + if (!recovering && point == stage) { + await ready.writeAsString(stage, flush: true); + await Completer().future; + } + } + + final actions = _DiskActions(directory, cut); + final store = FileAlarmJournalStore( + () async => Directory('${directory.path}/journal'), + checkpoint: (stage) async { + if (syntaxRecovery) await cut(stage); + if (stage == 'written') { + final staged = File( + '${directory.path}/journal/ownership-v1.json.pending', + ); + final state = AlarmJournalSnapshot.decode( + await staged.readAsString(), + ); + if (state.reset == ResetPhase.none) { + await cut('first-ownership-written'); + } + } + if (stage == 'renamed') { + final committed = File( + '${directory.path}/journal/ownership-v1.json', + ); + final state = AlarmJournalSnapshot.decode( + await committed.readAsString(), + ); + if (state.reset == ResetPhase.pending && state.completed.isEmpty) { + await cut('intent-renamed'); + } + if (state.reset == ResetPhase.complete) { + await cut('completion-renamed'); + } + } + if (stage == 'removed') await cut('journal-unlinked'); + }, + ); + final journal = AlarmOwnershipJournal(store); + final gate = LocalDataOperationGate(); + final owner = AlarmOperationCoordinator(gate, journal: journal); + addTearDown(() { + owner.dispose(); + gate.dispose(); + }); + final registry = fixtures.FakeAlarmRegistryRepository(); + if (!recovering) { + for (final step in ResetStep.values.where( + (s) => s != ResetStep.deliveries, + )) { + await File( + '${directory.path}/${step.name}', + ).writeAsString('isolated fixture', flush: true); + } + registry.records = [record('probe-owned', 73)]; + if (syntaxRecovery) { + final partial = File( + '${directory.path}/journal/ownership-v1.json.pending', + ); + await partial.parent.create(recursive: true); + // Explicit damaged-bytes fixture; this does not claim interruption + // inside the production write syscall or a simulated power failure. + await partial.writeAsString( + '{"version":1,"ownership":[', + flush: true, + ); + await cut('partial-first-pending'); + } else { + await journal.remember(registry.records); + } + } + final cleanup = AlarmRegistrationCleanup( + registry, + fixtures.FakeAlarmSchedulerService() + ..nativeObservationFails = syntaxRecovery, + fixtures.FakeFallbackAlarmNotificationService(), + owner, + ); + final result = await owner.cleanup( + () => LocalResetProtocol( + journal, + cleanup, + actions, + ).run(begin: !recovering && !syntaxRecovery), + ); + if (!recovering) fail('Requested kill point was not reached: $point'); + + if (syntaxRecovery) { + expect(result.recoveryRequired, false); + expect(result.intentRecorded, false); + expect(await actions.hasMarker(), false); + final repaired = await journal.read(); + expect(repaired.reset, ResetPhase.none); + // Fixture observations cannot certify Android native registrations. + expect(repaired.unknownOwnership, true); + for (final step in ResetStep.values.where( + (s) => s != ResetStep.deliveries, + )) { + expect( + await File('${directory.path}/${step.name}').readAsString(), + 'isolated fixture', + ); + } + } else if (point == 'first-ownership-written') { + expect(result.recoveryRequired, false); + expect(result.intentRecorded, false); + expect(await File('${directory.path}/database').exists(), true); + expect((await journal.read()).ownership.keys, ['localNotification:73']); + } else { + // Final unlink may have completed before SIGKILL; idle is then correct. + expect(result.isComplete || !result.recoveryRequired, true); + expect(await actions.hasMarker(), false); + expect(await File('${directory.path}/database').exists(), false); + expect((await journal.read()).reset, ResetPhase.none); + for (final step in ResetStep.values.where( + (s) => s != ResetStep.deliveries, + )) { + expect(await File('${directory.path}/${step.name}').exists(), false); + } + } + await File( + '${directory.path}/verified', + ).writeAsString('passed', flush: true); + }, + timeout: const Timeout(Duration(minutes: 3)), + ); +} + +class _DiskActions implements LocalResetActions { + _DiskActions(this.directory, this.cut); + final Directory directory; + final Future Function(String) cut; + File get marker => File('${directory.path}/reset-marker'); + @override + Future hasMarker() => marker.exists(); + @override + Future writeMarker() => marker.writeAsString('pending', flush: true); + @override + Future removeMarker() async { + if (await marker.exists()) await marker.delete(); + await cut('marker-removed'); + } + + @override + Future perform(ResetStep step) async { + if (step == ResetStep.deliveries) return; + final file = File('${directory.path}/${step.name}'); + if (await file.exists()) await file.delete(); + if (step == ResetStep.database) await cut('database-deleted'); + } +} diff --git a/tool/qa/verify_reset_crash_recovery.py b/tool/qa/verify_reset_crash_recovery.py new file mode 100644 index 00000000..c0e8115e --- /dev/null +++ b/tool/qa/verify_reset_crash_recovery.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Kill isolated Flutter test processes at reset cut points, then reopen. + +This verifies process death and filesystem recovery on the host. Providers and +deletion adapters are fixtures; it does not verify mobile OS delivery or backup. +""" +import argparse +import json +import os +from pathlib import Path +import signal +import subprocess +import tempfile +import time + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--flutter', default='flutter') + parser.add_argument('--output', required=True) + args = parser.parse_args() + root = Path(__file__).resolve().parents[2] + output = Path(args.output).resolve() + output.mkdir(parents=True, exist_ok=True) + results = [] + points = ['first-ownership-written', 'intent-renamed', 'database-deleted', + 'completion-renamed', 'marker-removed', 'journal-unlinked', + 'partial-first-pending', 'recoveryMarked', 'quarantinedPending', + 'recoveryVerified', 'recoveryPublished'] + command = [args.flutter, 'test', 'tool/qa/reset_journal_crash_probe.dart', + '--reporter', 'expanded'] + for point in points: + with tempfile.TemporaryDirectory(prefix='ontime-reset-kill-') as tmp: + directory = Path(tmp) + env = {**os.environ, 'D03_PROBE_DIR': tmp, + 'D03_PROBE_POINT': point, 'D03_PROBE_MODE': 'cut'} + with (output / f'{point}-cut.log').open('w') as log: + child = subprocess.Popen(command, cwd=root, env=env, stdout=log, + stderr=subprocess.STDOUT, start_new_session=True) + try: + deadline = time.monotonic() + 90 + while not (directory / 'cut-ready').exists(): + if child.poll() is not None: + raise RuntimeError(f'{point}: probe exited before cut; inspect log') + if time.monotonic() > deadline: + raise TimeoutError(f'{point}: cut point not reached') + time.sleep(0.1) + finally: + if child.poll() is None: + os.killpg(child.pid, signal.SIGKILL) + child.wait() + env['D03_PROBE_MODE'] = 'recover' + with (output / f'{point}-recover.log').open('w') as log: + recovered = subprocess.run(command, cwd=root, env=env, stdout=log, + stderr=subprocess.STDOUT, timeout=120) + if recovered.returncode or not (directory / 'verified').exists(): + raise RuntimeError(f'{point}: recovery failed; inspect log') + results.append({'cut': point, 'signal': 'SIGKILL', 'freshProcessPassed': True, + 'damagedBytesPreparedAsFixture': point in points[6:]}) + print(f'{point}: passed', flush=True) + (output / 'result.json').write_text(json.dumps({ + 'scope': 'host process death; fixture providers and deletion adapters', + 'mobileOsVerified': False, 'powerLossVerified': False, + 'writeSyscallInterruptionVerified': False, + 'results': results, + }, indent=2) + '\n') + + +if __name__ == '__main__': + main() From af28e8c8fd14d3fe9badff0667f032a372c1641c Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 10:38:46 +0900 Subject: [PATCH 34/59] docs: record reset recovery verification and time zone UI issue --- handoff/audit-2026-09-23-progress.md | 17 + plans/audit-2026-09-23/README.md | 7 +- .../d03/final/d03-manifest-policy.log | 1 + .../artifacts/d03/final/d03-python-policy.log | 16 + .../completion-renamed-cut.log | 2 + .../completion-renamed-recover.log | 3 + .../database-deleted-cut.log | 2 + .../database-deleted-recover.log | 3 + .../first-ownership-written-cut.log | 2 + .../first-ownership-written-recover.log | 3 + .../host-process-kill/intent-renamed-cut.log | 2 + .../intent-renamed-recover.log | 3 + .../journal-unlinked-cut.log | 2 + .../journal-unlinked-recover.log | 3 + .../host-process-kill/marker-removed-cut.log | 2 + .../marker-removed-recover.log | 3 + .../partial-first-pending-cut.log | 2 + .../partial-first-pending-recover.log | 3 + .../quarantinedPending-cut.log | 2 + .../quarantinedPending-recover.log | 3 + .../host-process-kill/recoveryMarked-cut.log | 2 + .../recoveryMarked-recover.log | 3 + .../recoveryPublished-cut.log | 2 + .../recoveryPublished-recover.log | 3 + .../recoveryVerified-cut.log | 2 + .../recoveryVerified-recover.log | 3 + .../d03/final/host-process-kill/result.json | 74 ++ .../d03/final/ontime-d03-final-analyze.log | 2 + ...ntime-d03-final-changed-source-freeze.json | 51 + .../ontime-d03-final-coverage-policy.log | 1 + .../d03/final/ontime-d03-final-coverage.log | 878 ++++++++++++++++++ .../ontime-d03-final-generated-policy.log | 1 + .../d03/final/ontime-d03-final-local-only.log | 1 + .../final/ontime-d03-final-source-freeze.json | 581 ++++++++++++ .../final/ontime-d03-p2-targeted-final.log | 73 ++ .../d03/final/root-final-analyze.log | 2 + .../completion-renamed-cut.log | 2 + .../completion-renamed-recover.log | 3 + .../database-deleted-cut.log | 2 + .../database-deleted-recover.log | 3 + .../first-ownership-written-cut.log | 2 + .../first-ownership-written-recover.log | 3 + .../host-process-kill/intent-renamed-cut.log | 2 + .../intent-renamed-recover.log | 3 + .../journal-unlinked-cut.log | 2 + .../journal-unlinked-recover.log | 3 + .../host-process-kill/marker-removed-cut.log | 2 + .../marker-removed-recover.log | 3 + .../d03/host-process-kill/result.json | 37 + .../d03/intermediate-boundary-45.log | 47 + .../artifacts/d03/intermediate-full-829.log | 834 +++++++++++++++++ .../artifacts/d03/kotlin-compile.log | 33 + .../artifacts/d03/reset-complete-en.png | Bin 0 -> 87555 bytes .../artifacts/d03/reset-complete-ko.png | Bin 0 -> 63591 bytes .../artifacts/d03/reset-pending-en.png | Bin 0 -> 74019 bytes .../artifacts/d03/reset-pending-ko.png | Bin 0 -> 59220 bytes .../artifacts/d03/swift-typecheck.log | 0 .../artifacts/d03/ui-capture.log | 3 + .../d03-final-validation.json | 65 ++ .../d03-journal-progress.json | 106 +++ plans/audit-2026-09-23/issues/D03.md | 47 +- plans/audit-2026-09-23/issues/U02.md | 172 ++++ plans/audit-2026-09-23/state.json | 45 +- 63 files changed, 3160 insertions(+), 19 deletions(-) create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/d03-manifest-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/d03-python-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/result.json create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-analyze.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-changed-source-freeze.json create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-generated-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-local-only.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-source-freeze.json create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-p2-targeted-final.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/final/root-final-analyze.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-cut.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-recover.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/host-process-kill/result.json create mode 100644 plans/audit-2026-09-23/artifacts/d03/intermediate-boundary-45.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/intermediate-full-829.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/kotlin-compile.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/reset-complete-en.png create mode 100644 plans/audit-2026-09-23/artifacts/d03/reset-complete-ko.png create mode 100644 plans/audit-2026-09-23/artifacts/d03/reset-pending-en.png create mode 100644 plans/audit-2026-09-23/artifacts/d03/reset-pending-ko.png create mode 100644 plans/audit-2026-09-23/artifacts/d03/swift-typecheck.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/ui-capture.log create mode 100644 plans/audit-2026-09-23/d03-final-validation.json create mode 100644 plans/audit-2026-09-23/d03-journal-progress.json create mode 100644 plans/audit-2026-09-23/issues/U02.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 79a18a7a..6e30bfa8 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -68,3 +68,20 @@ D03 다음: actual LocalResetActions(검증 가능한 marker/key/prefs/file/laun 공식 Dart3.12.2 runtime/bin/file_macos.cc는 rename(), file_linux.cc는 renameat() 호출을 확인했다. 같은 디렉터리 temp→rename로 이전 committed 파일을 유지하는 설계이며 directory fsync/갑작스런 전원손실 보장은 주장하지 않는다. Dart 공개 File.rename 문서만으로 원자성을 입증한 것이 아니다. Android backup_rules/data_extraction_rules의 root/file 제외는 존재하고 iOS 기존 excludeFromBackup bridge를 호출하지만 실제 mobile read-back/backup 검증은 남아 있다. 현재 sandbox는 workspace-write/auto-review로 바뀌었다. Flutter SDK 캐시, GitHub 네트워크, git index/ref 변경은 필요한 require_escalated 실행으로 자동검토를 받았다. 거절은 없었고 승인된 읽기/테스트는 완료했다. 기본 권한 Flutter 실행은 SDK telemetry/cache 경로로 실패했으며 제품 테스트 실패가 아니다. + + +## 2026-09-24 D03 제품 연결 및 독립 리뷰 보완 중 + +현재 pushed HEAD는 `0efdd3adb28293d3ff9f6b80bcd9091229384bd5`(A15 최종 CI 기록)이며 아래 D03 제품 변경은 아직 미커밋이다. 앞선 “제품 서비스 연결 전” 문단은 초기 milestone 기록이다. + +Root가 LocalDataResetService/LocalDataLifecycle bootstrap에 공통 reset protocol을 연결하고 DB/키/prefs/legacy credentials/native launch 각 단계의 read-back을 추가했다. pre-DI LocalStartupGate와 ResetAwareApp이 초기화 중 기존 앱을 폐기하고 KO/EN typed partial/retry/complete를 표시한다. 실제 알림 소유 기록은 preferences 삭제와 독립인 file journal에 먼저 기록한다. 실제 OS 호출이 반환되기 전에는 owner를 해제하지 않고 UI 10초 안내만 바꾼다. 손상된 legacy registry는 원문을 scrub하되 불명확한 취소 소유권 표시를 journal로 옮겨 보존한다. iOS 명시 fullreset의 app-scoped AlarmKit 전체 취소+조회는 별도 native call로 제한하고 ordinary OFF에서는 호출하지 않는다. Android는 잃어버린 legacy native ID의 부재를 확인할 수 없으므로 unknown을 거짓 완료로 바꾸지 않는다. + +중간 검증: shared production FileStore에 기대던 단위 테스트를 명시적 isolated owner로 정비한 뒤 full829 passed. 뒤이어 추가 reset boundary45 passed. 실제 별도 Flutter 프로세스 SIGKILL 6지점/새 프로세스 복구 통과(완성된 write checkpoint, fake provider/deletion adapters, 실제 모바일·부분write·전원손실 증거 아님). 실제 복구 위젯 KO/EN partial/complete 4 PNG를 430×932 logical /2x raster/200% text/번들 fonts로 생성해 root가 모두 열어 확인했다. Swift 실제 iOS26.5 SDK typecheck 및 Kotlin Android36+Flutter embedding compile 통과. `d03-journal-progress.json`과 `artifacts/d03/`에 중간 로그/이미지/해시 보관. 이 중간 결과를 이후 변경된 최종 소스의 전체 통과로 쓰지 않는다. + +원래 D03 에이전트가 사용량 제한 후 재개되어 독립 리뷰했다. P1: legacy reset marker만 있고 journal/registry가 비면 과거 취소 실패를 잃은 상태를 empty success로 처리한다는 결함. root가 marker-only unknown 보존 및 회귀 추가. 추가로 완료 service receipt를 캐시해 아주 늦은 progress mount/재호출에서 삭제를 새로 실행하지 않도록 수정했다. P2: malformed JSON/partial first pending의 영구 startup 차단. 원래 전담 `/root/grill_d03`가 실제 parse corruption과 I/O/unsupported/semantic-invalid를 구분하고 quarantine→최소 ownership 재구성→검증, reset 의도 없으면 데이터 삭제 금지 경로를 구현 중이다. 현재 Flutter 명령 소유자는 D03 전담이고 root는 제품 파일/Flutter 실행을 멈췄다. agent의 source freeze 보고 후 root가 최종 sourcehash/analyze/full/host-kill/UI·정책검사를 묶어 검증해야 한다. + +A10 원래 전담도 재개되어 실제 Q1~Q4/root 답변 완료. civil/instant 구분, device today vs original-zone calendar, unresolved zone/gap/overlap, timezone-neutral civil carrier, bundled tzdb 변경의 명시 확인과 restore staging 계약 확정. root가 ADR0021 보충 및 CONTEXT의 기존 rule-update 한 문장을 반영했다. A10 상세 문서는 agent가 작성 중이며 아직 GitHub 이슈 생성 전이다. U02 신규 전담 spawn은 agent thread limit으로 실패했으므로 생성/문답을 꾸미지 않는다. 다음 기회에 새 U02 전담을 생성한다. 이미 grill 완료한 A06 등은 D03 후 우선순위대로 구현 가능하다. + +## 2026-09-24 D03 최종 로컬 검증 + +D03 원래 전담의 P2 구현과 root 연결이 완료됐다. 전체872/집중71,87.99%,analyze/generated/local-only 및 native SDK compile 통과.579파일 재해시에서제품/test소스 변경없음, QA 대역에 명시적unknown설정만추가 후 hostSIGKILL11 모두통과. d03-final-validation.json 및artifacts/d03/final 참조. 원격CI와실기기검증별도. U02#609 게시/readback23/64,41개생성전. C08 전담grill진행, glossary root반영. 다음구현A06#598. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index abab8bd8..44048473 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -26,7 +26,7 @@ | A13 | [#592](https://github.com/DevKor-github/OnTime-front/issues/592) | 예약/안내·registry 후속 수정 원격740 tests/87.20%·APK 통과, 기기 대기 | | A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측 구현282db21f, 원격772 tests/87.44%·APK 통과, A15 실제 ownership 후속 로컬795 통과·기기 대기 | | A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 요청별 후속 재조정/교체 직렬화 구현, 최종 원격795 tests/analyze·87.66%·APK 통과, 기기 대기 | -| D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 실제4문답·취소 journal/중단 복구/부분 결과 기준 확정, A15 뒤 전담 구현 중 | +| D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 독립 journal·초기화 복구 구현, 최종872 tests/87.99%·host kill11 통과, CI·기기 대기 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | 실제3문답·정정 포함, 신선한 실제 타이머 전환 알림 기준 확정 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | 실제3문답·추가 정정 포함, DB 원자적 시작·run 시각·실패 재시도 기준 확정 | | C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 실제3문답·staging/gate 보충 포함, 구체 workflow·부분 결과·통합 의존성 확정 | @@ -37,8 +37,11 @@ | D04 | [#605](https://github.com/DevKor-github/OnTime-front/issues/605) | 실제4문답·migration 원자성/역사 schema/SQLCipher 확인·실패 보존 기준 확정 | | D05 | [#606](https://github.com/DevKor-github/OnTime-front/issues/606) | 실제4문답·bounded stream/자료 한도·시간대/그래프 검증 기준 확정 | | U08 | [#597](https://github.com/DevKor-github/OnTime-front/issues/597) | 홈 overflow 선행 수정18 tests/analyze 통과, 전체 매트릭스·기기 검증 미완료 | +| A10 | [#608](https://github.com/DevKor-github/OnTime-front/issues/608) | 실제4문답·civil/instant·오늘/달력·DST·명시 zone 편집·tzdb 변경 확인 계약,32개 검증 시나리오 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **21/64개**를 게시하고 원격 본문 동일성을 확인했으며 **43개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05까지 실제 문답과 백업·복구 검증 설계가 확정됐고 D03는 구현 대기다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +| U02 | [#609](https://github.com/DevKor-github/OnTime-front/issues/609) | 실제3문답·오프라인 zone 선택/기기 환산·DST/변경 재검토,23개 검증 시나리오 확정 | + +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **23/64개**를 게시하고 원격 본문 동일성을 확인했으며 **41개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 후 최종872 tests/87.99% 및 host kill11 검증을 통과했고 CI·기기 확인을 진행한다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/artifacts/d03/final/d03-manifest-policy.log b/plans/audit-2026-09-23/artifacts/d03/final/d03-manifest-policy.log new file mode 100644 index 00000000..29c18cf1 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/d03-manifest-policy.log @@ -0,0 +1 @@ +{"manifest": "android/app/src/main/AndroidManifest.xml", "sha256": "6b16f0fbaf8febcc2f9647450e40ca498462e2142f279c90666f86f77f14f210", "failures": []} diff --git a/plans/audit-2026-09-23/artifacts/d03/final/d03-python-policy.log b/plans/audit-2026-09-23/artifacts/d03/final/d03-python-policy.log new file mode 100644 index 00000000..d2a81b93 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/d03-python-policy.log @@ -0,0 +1,16 @@ +test_boot_permission_must_not_expire_on_newer_android (test_android_notification_manifest.NotificationManifestTest.test_boot_permission_must_not_expire_on_newer_android) ... ok +test_disabled_application_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_disabled_application_is_rejected) ... ok +test_duplicate_receiver_declaration_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_duplicate_receiver_declaration_is_rejected) ... ok +test_exported_disabled_or_renamed_components_are_rejected (test_android_notification_manifest.NotificationManifestTest.test_exported_disabled_or_renamed_components_are_rejected) ... ok +test_invalid_xml_is_reported_as_failure (test_android_notification_manifest.NotificationManifestTest.test_invalid_xml_is_reported_as_failure) ... ok +test_missing_boot_actions_are_individually_rejected (test_android_notification_manifest.NotificationManifestTest.test_missing_boot_actions_are_individually_rejected) ... ok +test_missing_delivery_component_is_rejected_even_if_named_in_comment (test_android_notification_manifest.NotificationManifestTest.test_missing_delivery_component_is_rejected_even_if_named_in_comment) ... ok +test_missing_exported_attribute_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_missing_exported_attribute_is_rejected) ... ok +test_receiver_must_be_direct_child_of_application (test_android_notification_manifest.NotificationManifestTest.test_receiver_must_be_direct_child_of_application) ... ok +test_source_manifest_can_deliver_and_restore_notifications (test_android_notification_manifest.NotificationManifestTest.test_source_manifest_can_deliver_and_restore_notifications) ... ok +test_valid_contract_without_explicit_enabled_is_accepted (test_android_notification_manifest.NotificationManifestTest.test_valid_contract_without_explicit_enabled_is_accepted) ... ok + +---------------------------------------------------------------------- +Ran 11 tests in 0.003s + +OK diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-cut.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-recover.log b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/result.json b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/result.json new file mode 100644 index 00000000..8f142336 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/result.json @@ -0,0 +1,74 @@ +{ + "scope": "host process death; fixture providers and deletion adapters", + "mobileOsVerified": false, + "powerLossVerified": false, + "writeSyscallInterruptionVerified": false, + "results": [ + { + "cut": "first-ownership-written", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": false + }, + { + "cut": "intent-renamed", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": false + }, + { + "cut": "database-deleted", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": false + }, + { + "cut": "completion-renamed", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": false + }, + { + "cut": "marker-removed", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": false + }, + { + "cut": "journal-unlinked", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": false + }, + { + "cut": "partial-first-pending", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": true + }, + { + "cut": "recoveryMarked", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": true + }, + { + "cut": "quarantinedPending", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": true + }, + { + "cut": "recoveryVerified", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": true + }, + { + "cut": "recoveryPublished", + "signal": "SIGKILL", + "freshProcessPassed": true, + "damagedBytesPreparedAsFixture": true + } + ] +} diff --git a/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-analyze.log b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-analyze.log new file mode 100644 index 00000000..1174232e --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-analyze.log @@ -0,0 +1,2 @@ +Analyzing OnTime-front... +No issues found! (ran in 2.2s) diff --git a/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-changed-source-freeze.json b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-changed-source-freeze.json new file mode 100644 index 00000000..66850c7a --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-changed-source-freeze.json @@ -0,0 +1,51 @@ +{ + "android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt": "566d7ebe8b369d4e72a45cc0d021fc9f7c2af17f147813edf0b3a621fd92c736", + "ios/Runner/AppDelegate.swift": "a79a1334f0d60a7850e9732539169eee283b282ef45338c3891d304907960110", + "lib/core/database/bootstrap_privacy_boundary.dart": "7e2b5071cbebb81dce56f27515423e71aaf4a8b4d2a31d3b8d2476d235af6658", + "lib/core/database/installation_key_store.dart": "7cfa038022656016309d06c3e5a68bd2f854f101f4e54c7a1ec5916cb6ffa990", + "lib/core/database/local_data_files_native.dart": "536fe8e157f69fc309f9c663ea29e504652aa505891c80325717d3af7b0cfcc0", + "lib/core/database/local_data_lifecycle.dart": "59f06dcdcd26bda9e84e488796097c7eb8eddc00fd10cd6f1e000cbe3734bdbd", + "lib/core/database/local_data_reset_service.dart": "d0575f88cdf7301339e315d274a88632d9d693bad09f247e20fd103c1bf46fbe", + "lib/core/database/local_reset_actions.dart": "e5434dc64feb35651ba66a5f4fd007a90aba7140220afb41036347129f1226f0", + "lib/core/database/local_reset_protocol.dart": "d5c7c0545cda1bd3b96741c79c37151d8316933592e5484658d8c52f61c79a36", + "lib/core/services/alarm_journal_store.dart": "eeb1dfc3ccc31410904300f1c354dc79ae20a7995122e34eab19b4bd98e96f93", + "lib/core/services/alarm_journal_store_native.dart": "7d8b052f35c81a7b553b5a074da92ba85e703c13d81fde7d6c1c8a1d953d4f67", + "lib/core/services/alarm_journal_store_web.dart": "d2bf8aef8f0b81c40e400c080751e64c8991b0db7dc4b7bf7f52f702a129da75", + "lib/core/services/alarm_operation_coordinator.dart": "16fcda8976015958d5bda525b3fd325ba5ab499f79665d98024ca299e0c3d845", + "lib/core/services/alarm_ownership_journal.dart": "30b439293ae0d9971a241a4e384e87d6db6b48a6995dcf20a7a1395b6c7d623b", + "lib/core/services/alarm_registration_cleanup.dart": "e42ebcc2cbc341e0ec7eda650afc88269c72ab668d36c068b216567a408f0a7c", + "lib/data/data_sources/alarm_registry_local_data_source.dart": "410ba53d780e6225c74b1c164c2d4729b2997ade4fb0368c1b3148a6aadee0f1", + "lib/data/repositories/alarm_registry_repository_impl.dart": "ac5fb3c920ac8185dd20841059b64ffe5b6d5f84ae846d73b7a6eac3fd8e567c", + "lib/domain/repositories/alarm_registry_repository.dart": "0798b435f240573dd06c779e23b6f9d07a6c43ffc612edc2b4728a0919224a6c", + "lib/domain/use-cases/cancel_all_alarms_use_case.dart": "bdbdc24b3555a6ff5cb39fa145c8c18a0996f6450bf2d5d76294774a61652d2f", + "lib/domain/use-cases/reconcile_alarms_use_case.dart": "45b6d7dc6a3f37f84085a915a945a6010584a4ea4242d4bfaf7791d5740494bc", + "lib/l10n/app_en.arb": "5623ec0393ecdab052b9051210aa2659ab19f799ac846a5a41ea571ca7d45e0f", + "lib/l10n/app_ko.arb": "c5a9bb8e4aa763d6c582071c567d520dce352df7070051ea4c6c82044b78c9b8", + "lib/l10n/app_localizations.dart": "14ac0b3e2383a8eb135ef3c61103290770712482315e72cf8443fed83e7b2f43", + "lib/l10n/app_localizations_en.dart": "db2359e1c11e2ff5d27e7842baf0f78f073cefc15ccc41240020a3d6b2256136", + "lib/l10n/app_localizations_ko.dart": "80f6390b5a06577a13c9a3eb6990d5351a891acc4da8fddec58b6e3199a3d757", + "lib/main.dart": "a198ca90f4b90620aee9e958d609bc463433b10a0577bea46439c20fcdaf3708", + "lib/presentation/my_page/my_data_screen.dart": "999189475c862367ecd6ec843a5b9937fc56f474d5b2367b99dc04cb079eb6b0", + "lib/presentation/startup/screens/local_data_recovery_screen.dart": "8aa7181d9125226bd970aa60a86bed61434a9f24bf0713af50919b696157f3a9", + "lib/presentation/startup/screens/local_reset_progress_screen.dart": "22f38e6e93a9b07742a49926977acac4532973ad20abd6203137252550dad8f8", + "lib/presentation/startup/screens/local_startup_gate.dart": "e962c2e30c15b5cec160d7ff63b6996b0eecdaa3a552efa5675521fd55b2d213", + "test/core/database/alarm_journal_recovery_test.dart": "d4861dbaea522c2811872172a51df80d8a4316d7519f9d2acc6699f5898c5c09", + "test/core/database/alarm_replacement_integration_test.dart": "717f0ae18f82d75228e7c88a2bb9d644d3cbbad5ddca3a974bf5c4bc332d3473", + "test/core/database/bootstrap_privacy_boundary_test.dart": "3a7246a1a4ff4042c1d31fc5fc60545885fb5ae3c1da833c32eff9b3c1a03847", + "test/core/database/local_reset_actions_test.dart": "5a22465c29a3bd96fecce055ae5aba721eec504833a0962bfc71a24f413071d0", + "test/core/database/local_reset_protocol_test.dart": "4d5f256dbb29dba29fc6b79b0da6bdd4c7024e2edfdbe19be9cee18fe465a99b", + "test/core/services/alarm_ownership_journal_test.dart": "c905fdb82a0630994568acf3ffab03d94bd2dbd304946f6a563a45784086d94b", + "test/data/data_sources/alarm_registry_local_data_source_test.dart": "e3f349245629404fbca999c18724177f70acf07e4f0bd30e23c60b0f3a30f772", + "test/data/repositories/alarm_registry_repository_impl_test.dart": "0b8060ced5059b4590a836c7eda138da3e4986c47d47b20d3518858f44524360", + "test/domain/use-cases/alarm_reconciliation_concurrency_test.dart": "621bfd5c1a14a48c5b39d97ebc37d16aa047ae7ed4be96fecc3b6fc96373b3e0", + "test/domain/use-cases/cancel_alarms_use_cases_test.dart": "4427e8e23f559ef727171345128ba1599b8bc55bf8883eedb0abbaad7316231d", + "test/domain/use-cases/delivery_reconciliation_test.dart": "f9d324dd9dacb1dc195239c038f13d00c59d91c5fca61f58088e835fe7531fb3", + "test/domain/use-cases/reconcile_alarms_use_case_test.dart": "37bcabb8fd546e363ad0c84d9c0810a625edc09c2bfebfd893a02046778d4646", + "test/domain/use-cases/recurring_alarm_integration_test.dart": "4b03a28c3f2f66259434d424cccf831eb38f37451f00c23a6a1dbcdbe5493142", + "test/helpers/isolated_alarm_owner.dart": "84a10eff3d2f4bbfca21e96c8d3e3d40fa47b11feecf0ce885df1c584ee22352", + "test/helpers/noop_alarm_cleanup.dart": "5b26450418c9b461dbf338d4a794a1535d7f4b711d7f14dab261bc67bab00c7a", + "test/presentation/startup/local_reset_recovery_test.dart": "f1afb3d065a4b4fbe8ee14aa66205cd8b74eea589da8fa011d00d3bbf6ec91f8", + "tool/qa/capture_reset_recovery.dart": "97cd0c27cc9cadcfb1d4f21dd5c894ec88e3eb84f3fd57ac0c151551594358dd", + "tool/qa/reset_journal_crash_probe.dart": "7a76a03f2dc08c17a8834ee0f8a1d9abb139ecc048e92d80970f3b17684f27e9", + "tool/qa/verify_reset_crash_recovery.py": "daa52732605ec0b75c165bf453d2085ca0f7eb03380e37162f40340ebab4de69" +} diff --git a/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage-policy.log b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage-policy.log new file mode 100644 index 00000000..f62aefea --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage-policy.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Coverage: 87.99% (12253/13926 lines) diff --git a/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage.log b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage.log new file mode 100644 index 00000000..dfdd4591 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage.log @@ -0,0 +1,878 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:02 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:03 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:03 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:04 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:04 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: only complete iOS OS observations resolve reconstructed uncertainty +00:04 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeUnmapped remains partial without guessing ownership +00:04 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeError remains partial without guessing ownership +00:04 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS fallbackError remains partial without guessing ownership +00:04 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS unownedNotification remains partial without guessing ownership +00:04 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: secure reset marker resumes deletion but Android uncertainty remains +00:04 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: explicit reset after damage cleans quarantine only after complete receipt +00:04 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":2} +00:04 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"complete","completed":[],"ownership":[]} +00:04 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"none","completed":[],"ownership":[{"provider":"localNotification","scheduleId":"a","id":1,"pending":true},{"provider":"localNotification","scheduleId":"b","id":1,"pending":true}]} +00:04 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: read I/O failure does not quarantine or authorize deletion +00:04 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryMarked recovers through a new owner +00:04 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:04 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:04 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:04 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:04 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: cleanup failure preserves both failures without private message text +00:04 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:04 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at verified recovers through a new owner +00:04 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at renamed recovers through a new owner +00:04 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at readBack recovers through a new owner +00:04 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryVerified recovers through a new owner +00:04 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryPublished recovers through a new owner +00:04 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: normal civil time resolves to one absolute occurrence +00:04 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: legacy marker clear failure stays closed and fresh owner retries after journal verification +00:04 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: legacy marker clear failure stays closed and fresh owner retries after journal verification +00:04 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: legacy marker clear failure stays closed and fresh owner retries after journal verification +00:04 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: journal-only uncertainty from temporary iOS observation failure converges on fresh startup +00:04 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: notification unmappedCount keeps uncertainty even with an empty entry list +00:04 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: invalid UTF-8 is syntax corruption, not an empty journal +00:04 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: quarantine alone cannot be mistaken for a new installation +00:04 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future staged version is not overwritten beside a valid committed journal +00:04 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: recovery refuses a future staged format even beside damaged main +00:05 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:05 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:05 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:05 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:05 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:06 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:07 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:07 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:07 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:07 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:07 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:07 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:07 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:07 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:07 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:08 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:08 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:08 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:08 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: independent file retains only cancellation identity across reopen +00:08 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/app_metadata_service_test.dart: package info metadata provider returns runtime app metadata +00:09 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: owned legacy pending is cancelled without clearing step notifications +00:09 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: cancel failure preserves only ownership tombstone and retry converges +00:09 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:09 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: successful cancel call without readback removal is not success +00:09 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: cold launch collects once and concurrent initialization is single flight +00:09 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: null +00:09 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:09 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:09 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: getter failure releases single flight and callback still works before retry +00:09 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback B arriving while cold A getter waits wins, raw legacy fields removed +00:09 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback before delegate installation preserves only safe latest payload +00:09 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: Noop to installed delegate cannot let slow cold A overwrite native B +00:09 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: data replacement invalidates delayed initial launch +00:09 +179: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart +00:10 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:10 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android read failure is unknown, not absent +00:10 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cancel needs public success and post read-back +00:10 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache absence does not hide cancel failure +00:10 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android remaining ID and failed post query are unconfirmed cancellations +00:10 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS OS observation preserves source and minimal ownership +00:10 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS read failure is unknown, not absent +00:10 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cancel needs public success and post read-back +00:10 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cache absence does not hide cancel failure +00:10 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS remaining ID and failed post query are unconfirmed cancellations +00:10 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit observation rejects unavailable, malformed and unrelated IDs +00:10 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit already absent is idempotent; present cancel requires fresh absence +00:10 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:10 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: platform cleanup failure is visible and retryable, without raw content +00:10 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: DB-unavailable cleanup removes legacy raw and does not touch unrelated data +00:10 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: localizedNotificationText selects Korean text only for Korean locale +00:10 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: isScheduleAlarmPayload detects native alarm payloads without suppressing schedule notifications +00:10 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule notification payload to the schedule start screen +00:10 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes five-minute prompts as early-start schedule starts +00:10 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule and preparation updates to the alarm screen +00:10 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString ignores null, invalid, and unrelated payloads +00:10 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: invalid schedule hints never route to nearest preparation +00:10 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForData routes decoded local payload data with the same rules +00:10 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:10 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:10 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:11 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real storage retains every failed platform ownership for the same Schedule +00:11 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:11 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=true +00:11 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: loadAll returns the current local registry +00:11 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: upsert replaces an existing schedule record and keeps others +00:11 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: deleteByScheduleId removes only the requested schedule +00:11 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: replaceAll deduplicates actual platform identity and deleteAll clears storage +00:11 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:11 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:11 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:12 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:12 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:12 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: ending following leaves active and historical rows and their preparations intact +00:12 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:12 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:12 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:12 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:12 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:12 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:12 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:12 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:12 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:12 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:12 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:12 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:12 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:12 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:12 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:12 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:12 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:12 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:12 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:12 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: an override cannot be moved into another occurrence preparation window +00:12 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:12 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:12 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedule date ranges as start-inclusive and end-exclusive +00:12 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:13 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_data_source_contract_test.dart: schedule local data source contract does not expose domain entities +00:13 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:13 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:13 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:13 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:13 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:13 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:13 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:13 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:13 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:13 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:13 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:13 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:13 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:14 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers map schedules to and from database rows preserving stored fields +00:14 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps users to and from database rows preserving profile values +00:14 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers empty users cannot be converted to database rows +00:14 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps places and preparation steps to database rows +00:14 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:14 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getUserById returns null for an unknown user +00:14 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getAllUsers returns all persisted users as domain entities +00:16 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:16 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return an empty preparation list when user has no steps +00:16 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:16 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps when rows are out of order +00:16 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should keep remaining preparation steps when a link is broken +00:16 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return each preparation step once when links form a cycle +00:16 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should return one preparation step by id +00:17 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:17 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:17 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: updatePreparationUser should update a preparation step +00:17 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:17 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:17 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: deleteSchedule should delete a schedule from the database +00:17 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getScheduleById should return a schedule from the database of given [id] +00:17 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:17 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] +00:17 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null +00:19 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:19 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: deleting a counted slot never extends or fills the series +00:19 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: last day is distinct from 31st, including leap February +00:19 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fifth weekday skips missing months while last weekday does not +00:19 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: weekly matching starts inclusively without adding the anchor day +00:19 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: daily intervals keep civil time and include the end date +00:19 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: spring gap is skipped and does not consume the count +00:19 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fall overlap requires an explicit first or second choice +00:19 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: new series starts at a future preparation and counts from there +00:19 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: range queries preserve original count and stable slot keys +00:19 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: unknown zone and exhausted search never report success +00:19 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: invalid and ambiguous rule definitions are rejected +00:19 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:20 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:20 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:20 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: updatePreparationUser should update a preparation step +00:20 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations createPreparationSchedule stores steps as an ordered linked list +00:20 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns empty for no steps +00:20 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId orders three linked steps when rows are out of order +00:20 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId keeps remaining steps when a link is broken +00:20 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns each step once when links form a cycle +00:20 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById returns the stored step contract +00:20 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:20 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:20 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:21 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:21 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:21 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:21 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation +00:21 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:21 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:21 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: skips a just-missed alarm instead of scheduling catch-up +00:21 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:21 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:21 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:21 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:21 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:21 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:21 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:21 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:21 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:21 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:21 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:21 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:21 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:21 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:21 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:21 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:21 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:21 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:21 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:21 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:21 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:21 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:21 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:21 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:21 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:21 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:21 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:21 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:21 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:21 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:21 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:21 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:21 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:21 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:21 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:21 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:21 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:21 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: deleted and finished schedules accept reconciliation before awaiting cancellation +00:21 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: reconciliation failures remain best-effort +00:22 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:22 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:23 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:23 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:23 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:23 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:23 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:23 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:23 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:23 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:23 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:23 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:23 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure does not undo committed start or skip its snapshot +00:23 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure after finish still clears completed runtime +00:23 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:23 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:23 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:23 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:23 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:23 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:23 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:23 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:23 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/cancel_alarms_use_cases_test.dart: CancelScheduleAlarmUseCase cancels matching native and fallback records +00:23 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/cancel_alarms_use_cases_test.dart: CancelScheduleAlarmUseCase preserves retry ownership when cancellation fails +00:23 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/cancel_alarms_use_cases_test.dart: CancelAllAlarmsUseCase cancels local alarms and clears the registry +00:23 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/cancel_alarms_use_cases_test.dart: CancelAllAlarmsUseCase clears an empty local registry +00:23 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_date_use_cases_test.dart: GetSchedulesByDateUseCase watches requested schedule date range +00:23 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:23 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:23 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:23 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:23 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:23 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:24 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:24 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:24 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:24 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:24 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:24 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:24 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:24 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:25 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart: unchanged preparation updates the schedule without saving preparation +00:25 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:25 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:25 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:25 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: each cutoff finishes without waiting for later edits to become quiet +00:25 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: a previous civil date in a negative offset still supplies a future alarm +00:25 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: a previous civil date in a negative offset still supplies a future alarm +00:25 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failure alone stops; a request from completion starts another pass +00:25 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: replacement waits for the actual late native Future before same-ID reuse +00:25 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: caller timeout cannot release ownership of an unreturned native Future +00:25 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: ordinary export busy does not invalidate a requested pass +00:25 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: replacement cancellation failure retains evidence and prevents replacement +00:25 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: delete mutation accepted during snapshot cannot be resurrected by replaceAll +00:25 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: registry write failure is not armed and a queued newer request recovers ownership +00:25 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: detailed OFF accepted after snapshot replaces old content in trailing pass +00:25 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: request after schedule snapshot receives its own latest pass +00:25 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:25 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=false +00:25 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:25 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:25 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: a registry-owned ID with a conflicting route is cancelled before repair +00:25 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=true +00:25 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=true +00:25 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=false +00:25 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=false +00:25 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native revoke and grant switch providers after cancelling the old registration +00:25 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:25 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:25 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:25 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:25 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:25 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:25 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:25 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: confirmed fallback orphans are targeted; unrelated and unknown ownership survive +00:25 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:25 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native failure uses only the actual fallback receipt after native cleanup +00:25 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed native write cleanup forbids fallback and keeps cancellation identity +00:25 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:25 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: off retains a cleanup warning and denial never becomes precise +00:25 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: no future registrations never becomes precise from capability +00:25 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: iOS alarm and notification language remains capability appropriate +00:26 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:26 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: empty user exposes null convenience values +00:26 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:26 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:26 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:26 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:26 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:26 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:26 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities calculates left time before leave with explicit now +00:26 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports late status after leave time has passed +00:26 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities fast-forwards preparation steps for late entry +00:26 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities single preparation step tracks elapsed time and diagnostics +00:26 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities marks all steps done for very late entry inside schedule window +00:26 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports display helpers for current and completed steps +00:26 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities skipCurrentStep marks only the active step done +00:26 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities completed and empty preparations expose safe display fallbacks +00:26 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities cache fingerprint changes when preparation step name changes +00:26 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities schedule with preparation derives start time and total duration +00:26 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities combines a schedule entity with timed preparation preserving fields +00:26 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleEntity diagnostics string representation includes schedule identity for diagnostics +00:26 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleState timing helper durationUntilPreparationStartAt uses injected now deterministically +00:26 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:26 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:26 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:26 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:26 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: minimal progress hydrates names/durations from current source only +00:26 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy exact fingerprint additionally requires all step fields +00:27 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy launch becomes a minimal versioned route, never a start command +00:27 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:27 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:27 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:27 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:27 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:28 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (setUpAll) +00:28 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:28 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:28 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:28 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:28 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:28 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:28 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:28 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:29 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: scheduled days render without hiding the selected day +00:29 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:29 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:29 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: header arrows move between months and request month loads +00:29 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:29 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:30 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits without scroll at 1.3 text scale +00:30 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: HomeScreenTmp subscribes monthly bloc for today +00:30 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits when today schedule exists +00:30 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: regular portrait home matches Figma hero and card geometry +00:30 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: banner clears the device safe area before rendering +00:30 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: today schedule tile truncates long schedule names +00:30 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: does not reopen schedule start on started-state ticks +00:30 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: view calendar button navigates to calendar screen +00:30 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:30 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:30 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:30 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +549: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +550: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +551: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +552: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +553: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +554: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +555: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +556: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +557: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +558: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +559: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:31 +560: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:31 +561: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:31 +562: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:31 +563: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:31 +564: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:31 +565: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is available before preparation starts +00:31 +566: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:31 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:31 +568: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:31 +569: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: delete action stays available for started unfinished schedule +00:31 +570: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:32 +571: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:32 +572: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:32 +573: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:32 +574: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:32 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:32 +576: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:32 +577: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:32 +578: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:32 +579: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:32 +580: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:32 +581: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:32 +582: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:32 +583: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:32 +584: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:32 +585: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:32 +586: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:32 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:32 +588: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:32 +589: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:32 +590: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +591: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +592: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +593: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +594: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +597: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +598: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +599: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +600: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +601: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +602: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +603: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +604: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:32 +605: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:33 +606: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:33 +607: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:33 +608: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:33 +609: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:33 +610: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:33 +611: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:33 +612: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:33 +613: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:33 +614: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:33 +615: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:33 +616: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:33 +617: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:33 +618: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:33 +619: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:33 +620: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:34 +621: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/constants/app_colors_test.dart: white opacity colors keep the documented alpha ladder +00:34 +622: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:34 +623: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:34 +624: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:34 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:34 +626: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:34 +627: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:34 +628: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:34 +629: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow same schedule re-emission preserves started state when early-started +00:34 +630: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish clears timed preparation and early session state +00:34 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow confirmation-required run stays stopped after prompt until explicit start +00:34 +632: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow mismatched fingerprint invalidates cached progress +00:34 +633: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start when early session and snapshot exist +00:34 +634: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start with only early session derives elapsed from session start +00:34 +635: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt loads schedule details and arms the prompt +00:34 +636: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects ended schedules and clears their alarm +00:34 +637: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects stale fingerprint unless it starts prep +00:34 +638: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm prompt validation failure clears only non-start prompts +00:34 +639: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish failure keeps the active schedule visible +00:34 +640: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: schedule events expose equality props for bloc deduping +00:34 +641: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:34 +642: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default marker when a scheduled day is not selected +00:34 +643: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default empty-day behavior when there are no events +00:35 +644: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:35 +645: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:35 +646: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:35 +647: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:35 +648: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:35 +649: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:35 +650: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:35 +651: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:36 +652: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:36 +653: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:36 +654: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:36 +655: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:36 +656: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:36 +657: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:36 +658: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:36 +659: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: date picker saves the initial date value +00:37 +660: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (setUpAll) +00:37 +660: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: button styles resolve enabled and disabled visual contracts +00:37 +661: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme builds app calendar tokens and copies overrides +00:37 +662: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:37 +663: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:37 +664: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:37 +665: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:37 +666: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route exits back toward its entry edge +00:37 +667: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: bottom navigation routes exit back toward their entry edge +00:38 +668: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: (setUpAll) +00:38 +668: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +669: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +670: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +671: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +672: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +673: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +674: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +675: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +676: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +677: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +678: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +679: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:39 +680: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:39 +681: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:39 +682: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:40 +683: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:40 +684: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:40 +685: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:40 +686: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:40 +687: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:40 +688: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:40 +689: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:40 +690: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:40 +691: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:40 +692: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:40 +693: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:40 +694: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:40 +695: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: changed preparation step time is submitted +00:40 +696: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:40 +697: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:40 +698: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:40 +699: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:41 +700: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:42 +701: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:42 +702: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:42 +703: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +704: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +705: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +706: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +707: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +708: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +709: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +710: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +711: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +712: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +713: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +714: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +715: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +716: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +717: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +718: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +719: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:42 +720: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +721: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +722: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +723: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +724: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +725: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +726: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +727: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +728: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +729: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:43 +730: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: new notification grant initializes local notifications +00:43 +731: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:43 +732: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:43 +733: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:43 +734: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets schedule start route values normalize legacy inputs +00:43 +735: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:44 +736: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:44 +737: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:44 +738: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:44 +739: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:44 +740: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:44 +741: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:44 +742: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:44 +743: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:45 +744: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:45 +745: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:45 +746: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:45 +747: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:45 +748: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:45 +749: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:45 +750: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:45 +751: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:45 +752: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:45 +753: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +754: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +755: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +756: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +757: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +758: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +759: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +760: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +761: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +762: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +763: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +764: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +765: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +766: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:45 +767: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue keeps overdue timer running after leave time +00:45 +768: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:45 +769: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog finish triggers finish flow +00:45 +770: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog finish triggers finish flow +00:45 +771: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: dismiss keeps notification delivery when fallback is available +00:46 +772: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: dismiss keeps notification delivery when fallback is available +00:46 +773: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets null schedule emission while already notExists navigates home +00:46 +774: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets stale notification after schedule end should redirect safely (spec-first) +00:46 +775: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:47 +776: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:47 +777: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:47 +778: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:47 +779: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:47 +780: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:48 +781: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:48 +782: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:48 +783: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:48 +784: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:48 +785: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:48 +786: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:48 +787: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:48 +788: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:48 +789: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:48 +790: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:49 +791: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +792: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +793: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +794: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +795: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +796: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +797: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +798: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +799: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +800: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:49 +801: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_bloc_test.dart: keeps a newly added step with time when name focus is lost +00:49 +802: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:49 +803: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:49 +804: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:49 +805: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:49 +806: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:49 +807: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:49 +808: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:49 +809: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:49 +810: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:49 +811: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:49 +812: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:49 +813: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:49 +814: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:49 +815: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:50 +816: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:50 +817: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_time_inputs_cubit_test.dart: place and moving time cubit validates fields and submits to form bloc +00:50 +818: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_time_inputs_cubit_test.dart: spare time cubit detects overlap and submits spare time +00:50 +819: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_time_inputs_cubit_test.dart: preparation changes update total time and notify the form bloc +00:50 +820: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: fills the bottom safe area while keyboard is hidden +00:50 +821: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: paints a white backplate behind the iOS keyboard inset +00:50 +822: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: does not add keyboard backplate while keyboard is hidden +00:51 +823: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +824: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +825: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +826: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +827: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +828: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +829: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +830: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +831: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +832: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +833: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +834: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +835: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:52 +836: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:52 +837: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: saves date and time picker selections through the cubit +00:52 +838: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:52 +839: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:52 +840: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: shows explicit choices for a repeated DST time +00:52 +841: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:52 +842: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:52 +843: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:52 +844: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:52 +845: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:53 +846: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:53 +847: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:53 +848: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:53 +849: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:53 +850: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:54 +851: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:54 +852: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:54 +853: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:54 +854: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_create_list_test.dart: create list forwards edits, reorder changes, and create request +00:54 +855: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:55 +856: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +857: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +858: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +859: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +860: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +861: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +862: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +863: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +864: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +865: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +866: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:55 +867: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:56 +868: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation errors explain invalid name and preparation time +00:56 +869: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +00:56 +870: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +00:56 +871: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +00:56 +872: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-generated-policy.log b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-generated-policy.log new file mode 100644 index 00000000..01eef09f --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-generated-policy.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Generated Dart policy is aligned: outputs are ignored and untracked. diff --git a/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-local-only.log b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-local-only.log new file mode 100644 index 00000000..71a9ce0a --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-local-only.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Local-only product boundary verified. diff --git a/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-source-freeze.json b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-source-freeze.json new file mode 100644 index 00000000..02aa67cb --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-source-freeze.json @@ -0,0 +1,581 @@ +{ + "analysis_options.yaml": "53c7e9cef5550ac4af49782d6be6c44a5c195f9a5e07a3cb9cb114337fefbbae", + "android/.gitignore": "b97dd21ea377acd068ce45903e6bd983dee58a3522bcea558e674821af080495", + "android/app/build.gradle": "c2bafd7ce6460bdaf58c683aa616e0aae8695941b03c8caf9fcab94a94bc1a9a", + "android/app/src/debug/AndroidManifest.xml": "30b0832ed8b92dfbd6ae2abfa939ccf26333d37600056b1572bd4a397a18a74b", + "android/app/src/main/AndroidManifest.xml": "6b16f0fbaf8febcc2f9647450e40ca498462e2142f279c90666f86f77f14f210", + "android/app/src/main/kotlin/club/devkor/ontime/AlarmLaunchPayload.kt": "9d2485c8c45e559a93f0d457dfa35ef5a05ebaf610a22a59788f75f7d55e01d9", + "android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt": "7c8d44256ee94eb74af779770ca11902b4485f53b6a9a94f10e87ad6520dee84", + "android/app/src/main/kotlin/club/devkor/ontime/BackupExportPlugin.kt": "4ad01cff546229ac1d941441710d0c63c226947289d38c33d01af1328a242728", + "android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt": "566d7ebe8b369d4e72a45cc0d021fc9f7c2af17f147813edf0b3a621fd92c736", + "android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmBootReceiver.kt": "2a85c577706925edc6eb972c7c59d1b2f0617da5ac452c4c5745bad7801b9311", + "android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmPolicy.kt": "439f8fa99257c2d6e8e4c9bb32a56ddf6acc284cd1a44f15c8f7f86765ee7a50", + "android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmReceiver.kt": "ba03fce43d0167d79189b037d0d3d205bad5518c682d17b48080273f2ab3e711", + "android/app/src/main/kotlin/club/devkor/ontime/NativeLog.kt": "74c67917c22da5a7faba4d8b643d520d322cf917f03574742e592fc647a3c985", + "android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png": "248a5bb6ff383e242f81316030c126a5e9ffe8d3092bf4204718cb6f2a565a93", + "android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png": "4d0defca54d623e6c308947add764a666fed70ec2f2285dac2b6d4e03bd6f641", + "android/app/src/main/res/drawable-nodpi/alarm_character.png": "e44360d5337f28675c24fd1248e8850284fcde77fcce58738bda3822ea2571a9", + "android/app/src/main/res/drawable-v21/launch_background.xml": "7ca6d5cec2169c5a98661a119db23a6f3399c8fef2256ff04f3a642831282478", + "android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png": "5fa49d0bab17898e933ef868fb09cde2c49df8085a19b59e44ee2c8b30d2f9d1", + "android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png": "7339796e7927395c0cea5871a868331598b1f33debb42a25711832dbfade928a", + "android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png": "bb2934fe04eac3c6db1d5b2ca4af60df2f53aca8a71216bf97e41ab55a5e8e80", + "android/app/src/main/res/drawable/alarm_bell_icon.xml": "fd5ddfc7a400565be91bc0d90387dd527e7c801298c7246beae957291324550b", + "android/app/src/main/res/drawable/launch_background.xml": "94433756fe10005ddd6fc55cc5c0b1e9f3c10b66a08ceac795ece6713d34e28a", + "android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml": "fe16ab84762f42e1166a008c9c77227fa3ba8399088bf9667e83d04bc4322af1", + "android/app/src/main/res/mipmap-hdpi/ic_launcher.png": "2d6f25d1a1d3bc007c1e5d52ae05657a3a1a9ca5a6cef5afab427b604dfb71e7", + "android/app/src/main/res/mipmap-mdpi/ic_launcher.png": "f60a3ebeea87468f1b0a149b845cd183bc7d6224693a2f71783fa523c4f26683", + "android/app/src/main/res/mipmap-xhdpi/ic_launcher.png": "5e67751be3b5e13bee586e061ab0343393f6f99f4b2d5c6cbc19f0e06cb8efec", + "android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png": "3621a41cc4f4c62c670ffd683ba32e8a5a25c81cb815184c73e3620d100ea25c", + "android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png": "eece4b38c16d0c547ee584c449e13e4efa00eebc1ee920b19057aac299c73eb0", + "android/app/src/main/res/values-night/styles.xml": "8c5bad5152f217e837519f9c15211d19f39bd45f5f67faf6dd81cb4b299e518f", + "android/app/src/main/res/values/colors.xml": "29819d32325cbdd04c27da9756b57174d98e11409fb4cc40fbdf53e084ab8407", + "android/app/src/main/res/values/styles.xml": "937d7c82f539c72eb94fd3a3773f29b3d3fef1997213c92cb632298cedf4bda9", + "android/app/src/main/res/xml/backup_rules.xml": "2c5c697984f4c52b526337ab61113f5843e0bfc6af4722374916ffe6da38dd9a", + "android/app/src/main/res/xml/data_extraction_rules.xml": "eda9dddce1e895de1efd716fe75e244187bf8f91a3bb7444c6ea1731e51c74a8", + "android/app/src/profile/AndroidManifest.xml": "30b0832ed8b92dfbd6ae2abfa939ccf26333d37600056b1572bd4a397a18a74b", + "android/build.gradle": "37bb5e2370be94561043add48ffcc5a939b438f226fc0666a6a1e6a826794d1a", + "android/gradle.properties": "87d24bdd0efb10bfd46d1cfc191ce01649eee934c65d7e682fd01e67463c0846", + "android/gradle/wrapper/gradle-wrapper.properties": "3d91f0932da99885c41e9dc4e85c9f9a2d3bfef4f0ad87473014dc0827a94884", + "android/settings.gradle": "c0db3daff92f286e29e4a8b56d0cca0868863b4344ae43a8b65fe668487aa184", + "ios/.gitignore": "37380775aeb2499662b7aca80f4ac4c95ae827c3ba2bfa179aa3e9af4812c623", + "ios/Flutter/AppFrameworkInfo.plist": "da12038f9b2688a8a26160fea2609fa8fcb11421448c109f09c0f573efe7698d", + "ios/Flutter/Debug.xcconfig": "690b8a8b1ae15ddd29b241429963e971bbc6720ab22f154cf8cf6b085b6d4925", + "ios/Flutter/Profile.xcconfig": "a82d7765b37116ee4821d2cd1d65942febf8032353757ffb0826eabde785b4a6", + "ios/Flutter/Release.xcconfig": "75abb819a9de1e0e41c835e7307bc64a506cac25a9a5c0b0d8253fa1c3d5d422", + "ios/Podfile": "db2da28b10a5ac3ed5d42dbf7208ece60fc886bd1f86f14335dde4037ba32350", + "ios/Podfile.lock": "0ffaa0cf3a05c7df4198475b9af0d04a11f95ed363f5d7fe3a6bdc090d3eaf56", + "ios/Runner.xcodeproj/project.pbxproj": "480c9e8b7d845c18d6fa04eb5be6956f1868a0fb50cbe98f41b2c84e211b1d30", + "ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata": "7f3b00b5c3fdb45242d7b87e1e5c4e25d1fa8129a16c94295ecc4e8ea2235c5f", + "ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist": "dfa0f9bb85b95ccba9c6811adadf8cb407fb5d52088e89db83ebc308ec448542", + "ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings": "11b247bc6796892d67c9a821d18604db4dd6a880981b34f38460b9866e6b247f", + "ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme": "37d1273a5a5d6a8092cb26fd2a35e64b619e6643b158d32e7c099bb7e6e3010b", + "ios/Runner.xcworkspace/contents.xcworkspacedata": "465e6de5660384eb6832e8078920ba6cd5305c445cb29a24a86a2fda4aa6b67e", + "ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist": "dfa0f9bb85b95ccba9c6811adadf8cb407fb5d52088e89db83ebc308ec448542", + "ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings": "11b247bc6796892d67c9a821d18604db4dd6a880981b34f38460b9866e6b247f", + "ios/Runner/AlarmLaunchPayload.swift": "29ab79ad928d8083dd84b360714ea387de67b0773fb647cf463520f2121463c1", + "ios/Runner/AppDelegate.swift": "a79a1334f0d60a7850e9732539169eee283b282ef45338c3891d304907960110", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json": "59aee64f32dab46ad775da6ea2f3fc4efe6e8a78289a9185cd0d2d7e055845dd", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png": "0c40d4e0b9c6709dd3bedc8df54e8a9f205615844109b65411b87fd3067dd954", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png": "6280036511d6ae04bb5c0b3be72e45c8f2969a9724809aaf7a6ebfa66a3c0d03", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png": "e8a25f1e1ce96770eb012c84b8e395e33bcb2dc6f84fbe207d2509c8bdd47672", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png": "e0c140f0dddf60cd59faf37adfe19620ec2da74c50e94a7a686a6f2bee7c3db4", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png": "7c2b580260781355a9e75c79bd0b1bc3075ef352e2b5cccfe9ae4b60ac371cdc", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png": "369d54b260b536a4562d3388bb8f58d9c3d4fa174a8916897b45bbbfb4878de0", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png": "d78b2184e2947566059e8e07832de015552776617ebaaed2dd6c836b060322b4", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png": "e8a25f1e1ce96770eb012c84b8e395e33bcb2dc6f84fbe207d2509c8bdd47672", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png": "7f9c1c6322da3d5d5dade699c9fb69e61487262032c1b730959c3678d6abe557", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png": "db9ba9d42f529088e6f131a829fa78b28c615a357dea07ff9ceeeea64140175e", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png": "8d89f349349c7d4bfe9bf877be8765ceb3c7fdd3d2195d9983926fa6940849fd", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png": "a713ee50bd7d78536e82987ba92f13772bb42bd130d8364704eedfce5f55596d", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png": "a6b0844196aaf5917f1b5b935f6066996f7ca4d21cf0a6bd2c87e350583e12d0", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png": "2eb3dbff8bbb80c7d80caef5ba98d05f39fa94bf7511b1d38491e7abbbf2ecbd", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png": "db9ba9d42f529088e6f131a829fa78b28c615a357dea07ff9ceeeea64140175e", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png": "0f461c78332af9d6915a0a2175cebf9362ab0731314c5748ccd8f989da784653", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png": "798c6d5408d248b09d011581fc69b197a2038e14b8cdeed1ee7ff9a94470e0a5", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png": "ee0c65cdf33100a6d4e9697b7cf6c6b9db413a720f59966608e8f4e76139a079", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png": "1fbacb4448e8e70136f805b3976fec6f61555d73bf15edd4ccd0266ff5025005", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png": "e4ab646c5e83f98453540ab3787beb9e52392d88ae094ad629c8312e6ff7db99", + "ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png": "800e94f2a3094b6b60750aea458cbba8eac3d3ca6350ff7fa4083355ec324d4f", + "ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json": "ccc7950fd5f5b82067c85f667940a9d59189151460761ece8098ca78daa19469", + "ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png": "ff18cc6e00efa7edc103c9b314f99f9681f96557035d1ad43ea13b218624debd", + "ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png": "bf0522d20a2aea2d875e46a8c148b012530d5319fafad3c0f71362a56cedb325", + "ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png": "89a44b699c3e3a969a47a5dfaa1947635b61137511ce408500024268c3f01708", + "ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md": "a985b626c4f7546a8ba392d135e9226c3547c8e2b46895f5f2bdf40d7b14ef9f", + "ios/Runner/BackupExportPlugin.swift": "8b19b8ecd01b30ef933cef6f67d1ccb48d067b5a8ca15a914318fd718322b7a0", + "ios/Runner/Base.lproj/LaunchScreen.storyboard": "9f97f1e36858b6deea5ebc1e3e98d819446982a76187e7d57343a6b4fe31b7e6", + "ios/Runner/Base.lproj/Main.storyboard": "fd367b6c43006d61b730f674970dd48b9f662c8f2264e52ffc1558102a2c9f2d", + "ios/Runner/Info-Debug.plist": "0a818d119bb65b76bc2846ce084224307abad559c8281f36ed2603e64878d07e", + "ios/Runner/Info.plist": "f3ef9720f23fc5470ede5cf86a18d726d3fa0e21aa3c6132abc347ef8bb65604", + "ios/Runner/Runner-Bridging-Header.h": "1a8d11d01d1cc92c650c9c9ed35299240052d4683a15a719a2315ef44185e074", + "ios/Runner/Runner.entitlements": "c1fcc053de371dc84b524f58540da62a42d11dfa3f66c3d329235a185914e733", + "ios/Runner/RunnerDebug.entitlements": "c1fcc053de371dc84b524f58540da62a42d11dfa3f66c3d329235a185914e733", + "ios/Runner/SceneDelegate.swift": "f62d6064e4ccddfd4cbf14e10f32822f1ba3de4d07cb3a89bbb7f34b207e2d0e", + "ios/RunnerTests/RunnerTests.swift": "21e51bea68137fa164ca71421595e8da557dc9f1fe6758147e9edd72eac78dfd", + "ios/TouchEn_nxKey_Installer.pkg": "16c5964c14fd841f24c45acb720012eb668477f8dfbd0b4dc47b055051f8662b", + "lib/core/backup/backup_crypto.dart": "f820982898eae103a9d556577f7ad40d8251398d32d59c389fff65aaadc23790", + "lib/core/backup/backup_file_export_port.dart": "5eac31ab73eed12dcc1a546dfa2368b7d04fce46d6bbb9f16b9eac1eedad61df", + "lib/core/backup/backup_password.dart": "042ad87c36c962c1f0aa4b53cfba013654605ddd1d0f5448b04b4324c58853d9", + "lib/core/backup/backup_service.dart": "9e9115611b34037085c81c458b2c448ff5db5f9aa6824127a3f895db8bfee651", + "lib/core/backup/recurring_backup_data.dart": "9c0d0a03cec3539fa9781d693a377a49528f0042d181ffeec50eb396c338f283", + "lib/core/constants/local_profile.dart": "c180bfc87721015db1352f8157678f3566c02046fa94c3f7468ea494baf2a99f", + "lib/core/database/bootstrap_privacy_boundary.dart": "7e2b5071cbebb81dce56f27515423e71aaf4a8b4d2a31d3b8d2476d235af6658", + "lib/core/database/database.dart": "f6b47a58701457f7531e0f06da7b7d31826bc91eb5da4c3154aaba25a6a8a52d", + "lib/core/database/installation_key_store.dart": "7cfa038022656016309d06c3e5a68bd2f854f101f4e54c7a1ec5916cb6ffa990", + "lib/core/database/local_data_files.dart": "1877a136a7c8f774b1a532af647dfa2e13da135b43af3b0cf25629258e4d1ed1", + "lib/core/database/local_data_files_native.dart": "536fe8e157f69fc309f9c663ea29e504652aa505891c80325717d3af7b0cfcc0", + "lib/core/database/local_data_files_web.dart": "ac8b4e6f6c1050ec3b42e65be3eb3824bd0477a24783f3311f2e958d013bff55", + "lib/core/database/local_data_lifecycle.dart": "59f06dcdcd26bda9e84e488796097c7eb8eddc00fd10cd6f1e000cbe3734bdbd", + "lib/core/database/local_data_operation_gate.dart": "ca8bfc6f0524d72bd31fb6edd7b460c9fbb920b82a8115eee765903a855df431", + "lib/core/database/local_data_reset_service.dart": "d0575f88cdf7301339e315d274a88632d9d693bad09f247e20fd103c1bf46fbe", + "lib/core/database/local_reset_actions.dart": "e5434dc64feb35651ba66a5f4fd007a90aba7140220afb41036347129f1226f0", + "lib/core/database/local_reset_protocol.dart": "d5c7c0545cda1bd3b96741c79c37151d8316933592e5484658d8c52f61c79a36", + "lib/core/database/open_database.dart": "5147fbe63d9b43fefc9497deef8cdba06a7454c524df3cdde3b098fd79304a28", + "lib/core/database/open_database_native.dart": "25a5769f5fd0bf7b4379d6ac943ca4c51e0871589062043be5ca3ebb3c971310", + "lib/core/database/open_database_unsupported.dart": "26de98197064ba45d8d574d639a1f85d01338a55e6bb0e5302ac394c6057db23", + "lib/core/database/open_database_web.dart": "ec0fe47260816155ac0d48d25e2a20d8a5a10684c64337c8bbba7572a759138f", + "lib/core/di/di_setup.dart": "4d77f4936d65761d000b78b14536d0e1602629fc6a209bd67327c03b580060d6", + "lib/core/logging/app_logger.dart": "9e45b3a9b157ebd49d960550a5ca88ef71a6beecaccc18a51cb0f960178b6523", + "lib/core/services/alarm_journal_store.dart": "eeb1dfc3ccc31410904300f1c354dc79ae20a7995122e34eab19b4bd98e96f93", + "lib/core/services/alarm_journal_store_native.dart": "7d8b052f35c81a7b553b5a074da92ba85e703c13d81fde7d6c1c8a1d953d4f67", + "lib/core/services/alarm_journal_store_web.dart": "d2bf8aef8f0b81c40e400c080751e64c8991b0db7dc4b7bf7f52f702a129da75", + "lib/core/services/alarm_operation_coordinator.dart": "16fcda8976015958d5bda525b3fd325ba5ab499f79665d98024ca299e0c3d845", + "lib/core/services/alarm_ownership_journal.dart": "30b439293ae0d9971a241a4e384e87d6db6b48a6995dcf20a7a1395b6c7d623b", + "lib/core/services/alarm_registration_cleanup.dart": "e42ebcc2cbc341e0ec7eda650afc88269c72ab668d36c068b216567a408f0a7c", + "lib/core/services/alarm_scheduler_service.dart": "68cdd5deef016088c00fffb8777fb15a02b5a8a987798a639ac8abae15b214ab", + "lib/core/services/app_metadata_service.dart": "851293b1b21c791b4deca12f69095d6aafa615f0a1bb62dff93a9c9c82b80d6b", + "lib/core/services/detailed_notification_preference_service.dart": "1ffb69c38ceb33621cdf218884eb1e4b9cc7a8e387f4695e2758f09d0b5dde6f", + "lib/core/services/device_info_service/device_info_service_mobile.dart": "45d9651212b63ffd59b9d77a71ac415903cf6cad36d4648c8fdd4e79cb558f52", + "lib/core/services/device_info_service/device_info_service_unsupported.dart": "664d1e6f5c2616163e1feec9798f5491b35020248b055386eb4069bfd1784491", + "lib/core/services/device_info_service/device_info_service_web.dart": "40aa5eb469a1417009a077dc492826ed7cae794181513786f88d660470814adc", + "lib/core/services/device_info_service/shared.dart": "2c453d3dd57f7fe47c1c84b2a9fc5600162b3ce53186110e311b917ef3a9f954", + "lib/core/services/fallback_alarm_notification_service.dart": "3e15e7ce0fb5fed325ed530e00a484a22c43635daa987325c24b09e2ab64adaf", + "lib/core/services/js_interop_service.dart": "7d5889aef25ea3bb294dae2d9320ade32e94d6e09c602d05a40c3a29989eec09", + "lib/core/services/js_interop_service_stub.dart": "2b24f83a00bd811505159d508eea84e4333ffbfaebb62a3236dafef841c217b1", + "lib/core/services/js_interop_service_web.dart": "586702f62aba8ce3a23cdba9634125dce631d76819b46766b4c5e7eadb246774", + "lib/core/services/local_time_zone_service.dart": "61c16945e525bd6edce9df073162149f31667b8bc33087887b7c111b74ca3ca4", + "lib/core/services/navigation_service.dart": "c03431988dc6503698773fee686d2d3587fb8ee16a75c3b0d65be12d39535146", + "lib/core/services/notification_content.dart": "fc47b2c91b6e2f84fb84a4774e8a98b2a14e210d3d2529f38e9015df538a9750", + "lib/core/services/notification_routing.dart": "b8e1d317274aff61479c642aeea34031eca35e93e1fc747a64c12f8d5ccefb9e", + "lib/core/services/notification_service.dart": "2a897790a49dd7b1bf8c017ff9e1e8892ee1dfcafe743aab855dbb5abe6f1944", + "lib/core/services/notification_tap_router.dart": "4ab9b03c587061d54aedd6d1f729c01bcd633e94401584083ec55e434d8c5783", + "lib/core/services/runtime_privacy_migration.dart": "2af68dea5f0ad56fcc356e7c7dfcb60f476de4c749c60b2dc1afdff11b171100", + "lib/core/time/civil_time_resolver.dart": "8c1922403408b8597447479b1c3edd3b1018af1ba2f98655373d0e4aeb390ffc", + "lib/core/utils/json_converters/duration_json_converters.dart": "634896699c8e7305e6cec412e4519a1572a36a1133aef853ebe7ef26c3198002", + "lib/core/validation/local_input_limits.dart": "bc8633aed542be5ed18de090580faefa785858e7b6022ea61c3cbe2c7f46ff38", + "lib/data/daos/place_dao.dart": "092548b6f057d4ab3b486ecf20046e5c7820fee3f2d560433692a58509183d3f", + "lib/data/daos/preparation_schedule_dao.dart": "55e99d5f966a30f285c355c44c5868f7ab90de33a5c88853ea93343bbdde3f18", + "lib/data/daos/preparation_template_dao.dart": "f8ca77932a9169e7e2a2c49779d9b07e75752f959f3d60be8174d2502b43b565", + "lib/data/daos/preparation_user_dao.dart": "a4017daa822bcdb8384f234c9d7f3e8c297ea290951236122ce2c5f843bf2e3f", + "lib/data/daos/schedule_dao.dart": "0adbad83333c4ab6c0d85b901aead04185051369a8d8a2298464b47436404b3d", + "lib/data/daos/user_dao.dart": "37f08ff913035b2118b8d183f29119ca078af0f0e371bd44302200a1cad37ca6", + "lib/data/data_sources/alarm_registry_local_data_source.dart": "410ba53d780e6225c74b1c164c2d4729b2997ade4fb0368c1b3148a6aadee0f1", + "lib/data/data_sources/early_start_session_local_data_source.dart": "86924c7d450adeab3d997dac2dbf89bc0b9c1d14cf8f168097f20768003b6429", + "lib/data/data_sources/preparation_local_data_source.dart": "cdfe40002812d5b085876b4688c3cffa51d922134b0270528e980227f1824773", + "lib/data/data_sources/preparation_with_time_local_data_source.dart": "7be9a5e73e0a0bd0c2574590824728609aa5b9ef2153366339855e18a37adda1", + "lib/data/data_sources/schedule_local_data_source.dart": "e6316ed0995eab1a3b5c1b25211f5295e1ac67515a295220b7317e0c69080b57", + "lib/data/mappers/domain_persistence_mappers.dart": "4ef0207d92e43c6c54b9c9554b017712401c7acceeb42e412d3833fe7be8e1a0", + "lib/data/mappers/recurrence_codec.dart": "ae83f2e6f2cf2bb498deca151b52ffaac3999c5bcf239783a8a2971c8ad71c41", + "lib/data/models/scheduled_alarm_record_model.dart": "1366c69727bae27a06f9887939ea7e7ab5c6a5eed432513ec6ecb92d765c4233", + "lib/data/repositories/alarm_registry_repository_impl.dart": "ac5fb3c920ac8185dd20841059b64ffe5b6d5f84ae846d73b7a6eac3fd8e567c", + "lib/data/repositories/alarm_repository_impl.dart": "2e2a8076acfe655e11606e073eb839500c67fdabded7541452a66160a73537b7", + "lib/data/repositories/early_start_session_repository_impl.dart": "ebf3e50cbceab742ba66ca859f2b80d34bcecd3073163877f69f9cdfcc9ff2d2", + "lib/data/repositories/preparation_repository_impl.dart": "74a73dd590ba208f14f2e15696659a797cfab9b7216c4b659ea18f5fbf5746e0", + "lib/data/repositories/preparation_template_repository_impl.dart": "3f5504d6738c21c8ead6e95d9c7b84b3d04b25f156967e0f01bc63b52f50a657", + "lib/data/repositories/recurring_schedule_repository_impl.dart": "d789ba4efe0c5040d9a62f0200a9fb8695e64f6cd3c86071bfd04ca95e1cce6a", + "lib/data/repositories/schedule_repository_impl.dart": "c415ac5512be2b4e693781330a08e407ad4e55c4585a58b9f30edcf31f1b9c2a", + "lib/data/repositories/timed_preparation_repository_impl.dart": "33ad13ae5fb72eefb8bad6e177adffc3887f76f44e89d9ec0010fcd50df528bc", + "lib/data/repositories/user_repository_impl.dart": "5e2aded4b9f0c1fce796e3a777a262127e1acfe5462fca95d71c5ad9d1e46783", + "lib/data/tables/places_table.dart": "4faa94d0ea174eb72fb6af97b4f83bb6086c22d4bd99b5eb96b8f087d799a933", + "lib/data/tables/preparation_schedule_table.dart": "4e93927beeefd4d4431a84bcf31b6a9fdba98209cef25853b1d1b4f9104d8614", + "lib/data/tables/preparation_template_step_table.dart": "d7554446891571289182708cb71cc28582d199b4149025001b6ba79b2b369428", + "lib/data/tables/preparation_template_table.dart": "42a8943a79f459be733883655178bd2e7c3c2409b1ade2ff6a788b72d76d57f9", + "lib/data/tables/preparation_user_table.dart": "df066df5176f094017b2a33b86f520110d3aa887dd00441079cced8cfe09dc03", + "lib/data/tables/recurring_schedule_tables.dart": "4f0697adb6726dee886bca11c5c26582700d58bd7d53bce2e347a90a9ee739db", + "lib/data/tables/schedule_with_place_model.dart": "a29987db650796edc7443162b0a959db2e777b915cafaf20bd71e17c5a79aa9f", + "lib/data/tables/schedules_table.dart": "509cfd2257720158450f26e3d0ab37dec4b1cde13312e2acedbabcfd2af9f61c", + "lib/data/tables/user_table.dart": "fc3ceac20b28f157ec08ff5b18b4e2bf6cbe89dac0a01907d409822482ba17b3", + "lib/domain/entities/adjacent_schedules_with_preparation_entity.dart": "e8e3103da76a609dff08908e492056c70cfbc32a5bc20640e01ba8dc6562a831", + "lib/domain/entities/alarm_delivery_policy.dart": "28e99dac2867c63feb71ae2256695f165e4b8a8c067168223a93dbf8d00f747f", + "lib/domain/entities/alarm_entities.dart": "8b293fe26b9e427767f0e27e1c241f3fa67f355c3be63c7db03319f49ba2f140", + "lib/domain/entities/delivery_observation.dart": "2f0623716afe22b99e1d6026ab5c82019d054569b032b77c7abfc7af89d6de5f", + "lib/domain/entities/early_start_session_entity.dart": "a114fea000555c64efd986ed20ba1905b234322809a812eb85dd7891cca17ad3", + "lib/domain/entities/notification_route_payload.dart": "f0a05b1014e17e1cb6e3127af765d873ea551fe5129b74b5ff4e9bfb5c75c7c2", + "lib/domain/entities/place_entity.dart": "de73d57dde841e9c1a995d704d9cd21620a7319c23aef79ba8f81794a35bec3c", + "lib/domain/entities/preparation_action_event_entity.dart": "cd19303623aa39680c7255cbf3ec2ba9e3643f58c95b6aebe985ca6401e821c3", + "lib/domain/entities/preparation_entity.dart": "d502cc73ddbefcd188948cee3ccf99f30b078a147457cf777a6326d96d98bae0", + "lib/domain/entities/preparation_snapshot_validation.dart": "a71cc6843d1cd6fef8c729ff10af52331b69378585111d58a3db63834c312dda", + "lib/domain/entities/preparation_step_entity.dart": "70d16144e4700a42fa50479c64bc0ae616f8e3c3cad271354845ed2159af36f2", + "lib/domain/entities/preparation_step_with_time_entity.dart": "bc07d5132050cf27cca687a46b4faef07898624bd9cf4828402fa77937e68354", + "lib/domain/entities/preparation_template_entity.dart": "e2fa8c32836461e08fdcf90b786e0ccd692053a1eff9b951b091146aa07a77be", + "lib/domain/entities/preparation_with_time_entity.dart": "73431e54375f34b457ce8796ba842cb21b4895fb0a19c5baca198e05ba45c31e", + "lib/domain/entities/schedule_entity.dart": "65c0775d233d4b3e147d198d69f867feb938fc749e95e97dd180651b34e43c82", + "lib/domain/entities/schedule_not_found.dart": "7d2f02cf9233919f56f2fb52901a9e32974c729f5c71ad514bd7feeabf65155e", + "lib/domain/entities/schedule_notification_status.dart": "93e994a46aab2768b33eb6d9f132ca9e20f1fef7f521b860ad1809abc7dcbcc8", + "lib/domain/entities/schedule_preparation_mode.dart": "0bc04629e912e65ef01111fda8d6652d5dc9c72aa1cbab4067ea7fce13f27516", + "lib/domain/entities/schedule_with_preparation_entity.dart": "b99c5100609e420c6081a2b15a684ba21abf6542bc78f4c93fa3e3e0914ece59", + "lib/domain/entities/scheduled_notification_content.dart": "f2f6d2fcf72c9f10b42b659589c7cae641c357b14135e27b13ad96a8920ae95e", + "lib/domain/entities/timed_preparation_snapshot_entity.dart": "29d44ca08aa0846fe225b458280856f09de176221bdef42c01f025abb874defa", + "lib/domain/entities/user_entity.dart": "c072da5b1aee09a57e3175003205804f79e7d91c555541fa04d9565f25cec5a9", + "lib/domain/recurrence/recurrence_engine.dart": "dfc0af396929c049f68a6d132c844bdefcad7934701b66bab54475bc0cef79ea", + "lib/domain/recurrence/recurrence_rule.dart": "f362bd037933f27a729f9be73b0972619948adeae6f060e54039b0d9bf4aa66e", + "lib/domain/recurrence/recurring_schedule.dart": "1132d79bc77b0ea177fc9df8695eee2de7d7478649a72e0a4db04fc4fc5ec450", + "lib/domain/repositories/alarm_registry_repository.dart": "0798b435f240573dd06c779e23b6f9d07a6c43ffc612edc2b4728a0919224a6c", + "lib/domain/repositories/alarm_repository.dart": "101599fff6ff6ba21629f953b694edd40a5ec16cb69bd6b5a3e68e665cbe1dc0", + "lib/domain/repositories/early_start_session_repository.dart": "3a099407358b2071a300e8d23423d7605e8392ad0b9690a633edbbfcac797eef", + "lib/domain/repositories/preparation_repository.dart": "0ac5a1eaefe0a49e8a0666e4f354adf083bd7c1dda34c9d0ec79731005e83015", + "lib/domain/repositories/preparation_template_repository.dart": "7f56dc6f0bd337036aa94850ab21edfc459923bd3d0fe80af9c2649d6406c971", + "lib/domain/repositories/recurring_schedule_repository.dart": "e5e7ff05b838ead79897d74a9e80ca4490f81da1b9d113aa37dbc77df4c6f65b", + "lib/domain/repositories/schedule_repository.dart": "32921ef38f1d58929256b61755b93e8d1f6ca686e13047eaf978d333b1271aae", + "lib/domain/repositories/timed_preparation_repository.dart": "0bde910c7a0abe4a2cd50d441d22c000748a42eeeeae4489da732f9aceb7afe2", + "lib/domain/repositories/user_repository.dart": "65b28a130e4675d94b522cd32ca8ddc2b0146163ea35f8adb45d468e94bca935", + "lib/domain/use-cases/cancel_all_alarms_use_case.dart": "bdbdc24b3555a6ff5cb39fa145c8c18a0996f6450bf2d5d76294774a61652d2f", + "lib/domain/use-cases/cancel_schedule_alarm_use_case.dart": "910edcb21f162d0720c4bd3ee78b40ef05abb571b02eeae7ae4a0e4536bc4939", + "lib/domain/use-cases/clear_early_start_session_use_case.dart": "e03b72e99c2ab379ba16d7db5d6247b39ada25a2e615a1daf5608ac62e781acf", + "lib/domain/use-cases/clear_timed_preparation_use_case.dart": "67dea0a0619b82be5113881dca4259cbc0cf4fe41eedce5902e0a809934280e4", + "lib/domain/use-cases/create_custom_preparation_use_case.dart": "9e65bad6207798a9b5388e49d9d41e47e58334a7ff6a2cbe629c8f7976c179e6", + "lib/domain/use-cases/create_preparation_template_use_case.dart": "b2d2eb7688917a8383bf0e07e54aef240e0f7c37c15314bfe7707bf88a4b3175", + "lib/domain/use-cases/create_schedule_form_submission_use_case.dart": "e07be9ab84c84cc4e7193519ee72004bc06bf0ef488e77a128f882813b04b7e7", + "lib/domain/use-cases/create_schedule_with_place_use_case.dart": "3352e6ac345b58f8d2d07372d32152555447f276fe7c95336d51c6ca4c876356", + "lib/domain/use-cases/delete_preparation_template_use_case.dart": "2853fc103bdd5d34003ed4ccec7fbdd61450aea1ec0663b49ab1b01880a8ec32", + "lib/domain/use-cases/delete_schedule_use_case.dart": "0b0b0e24fa60b8689ef2ff01be8e70aed58d71421296804d89d8dd46d6c411ba", + "lib/domain/use-cases/finish_schedule_use_case.dart": "64156990609c524673bd00c1560d16677dfc3c46d3e858b52bcd5c55f772b827", + "lib/domain/use-cases/get_adjacent_schedules_with_preparation_use_case.dart": "0527d6247978dfdf4177e02796f201bdc306764dd9e23662fce6081b6732d835", + "lib/domain/use-cases/get_default_preparation_use_case.dart": "fd899f4c11929db72c380ec5793517e6a00a5f2aa6c79219759b379a1e780619", + "lib/domain/use-cases/get_early_start_session_use_case.dart": "2f59b8e5835f032c9477458a3f376fccad6ba0a3c94fa6a91fef890c063f2299", + "lib/domain/use-cases/get_nearest_upcoming_schedule_use_case.dart": "fb70e85f7eb628ade7ed4f69c43785b67a2e797accec9a6b7a889074e515e255", + "lib/domain/use-cases/get_preparation_by_schedule_id_use_case.dart": "ab8f51433b9efd6789ebef58eca4b9051d9e6faca0d0ea9bb181aeb7f5582de9", + "lib/domain/use-cases/get_preparation_template_use_case.dart": "4c4a5dfbb25a6b57f073260f2fe2a48dd65d465111008eae2d1e73a750c57a77", + "lib/domain/use-cases/get_preparation_templates_use_case.dart": "53115e7e9bd3eda925961d9a5afd74d8efa7df6d380d0639386e350274f4c220", + "lib/domain/use-cases/get_schedule_by_id_use_case.dart": "9d9937d3abf864b3739020614b5ffa0fd8dcaa1d6aca028fa2a2488a459b54ce", + "lib/domain/use-cases/get_schedules_by_date_use_case.dart": "37993cdcee0db59c30903fce7a1b86c21cc2a97a5495045820dad6003b938620", + "lib/domain/use-cases/get_timed_preparation_snapshot_use_case.dart": "f38dcdd3ad69a1847f0fdd043cc6b8790dac24835de133cef379d6bb64eea80e", + "lib/domain/use-cases/load_adjacent_schedule_with_preparation_use_case.dart": "3c8e688ae097ea69a44402339809d4739f0da503a7244e25d5df5a081dd662e3", + "lib/domain/use-cases/load_preparation_by_schedule_id_use_case.dart": "9e4557715b3391cbe74b8c0ef6992f008ecc2d42279322c280703ad5438fe5c8", + "lib/domain/use-cases/load_schedule_form_draft_use_case.dart": "f801ee00ad9ee066e93fa1e371c47975be9d3a14183672ed5cf5b4395632999d", + "lib/domain/use-cases/load_schedules_by_date_use_case.dart": "1b30e58409b0e6e1b40515c3b5f5eb37a9202986c4f8823a39a24d0361302ccb", + "lib/domain/use-cases/load_schedules_for_month_use_case.dart": "5d9fbc5bab20825c397c9738e24c74d60c34bf18318faf35e38800af298a7685", + "lib/domain/use-cases/load_schedules_for_week_use_case.dart": "589374272e31c7e06cf46f3af807ac323b4f068afcf78c1b969d61fc5b9f9734", + "lib/domain/use-cases/load_user_use_case.dart": "1be68c80a72346c4126fd07be03547b5acd5c79ac5aedc865ebc504220950d7d", + "lib/domain/use-cases/mark_early_start_session_use_case.dart": "ffdd344fff94b5f481352f258b42a21f43f3a56175348794297e2083379256e0", + "lib/domain/use-cases/onboard_use_case.dart": "b5425eb8369af2e7bae074b4f4c4aa472f8c3f2f6502893791019655df1c285a", + "lib/domain/use-cases/reconcile_alarms_use_case.dart": "45b6d7dc6a3f37f84085a915a945a6010584a4ea4242d4bfaf7791d5740494bc", + "lib/domain/use-cases/recurring_schedules_use_case.dart": "7d43d7d408543bccd49f2a85e8aa47c607678bc9f7e3fa3cf1ecf434c16367bc", + "lib/domain/use-cases/save_timed_preparation_use_case.dart": "d12db73deffddee2de258f30b664656432d05860ed9a239b0e1da07fa9d8fd8b", + "lib/domain/use-cases/schedule_form_submission.dart": "8ab2f06821b21029a1e733633d6343578a158caa1e8a7de7311f46a94515ff91", + "lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart": "a74af417500c2808c6dcf47acbe1e4a6074428d5076baa94cc6c2071bc37c6e1", + "lib/domain/use-cases/schedule_preparation_session_use_case.dart": "94a0ac310c64127682cf26cb1d46568a767d6a106e68c8726281038216938a22", + "lib/domain/use-cases/start_schedule_use_case.dart": "acf490fd3ae19994d8f6caca0293f5123ccd37c2d3e86ec10f0b895ceee8d1f2", + "lib/domain/use-cases/stream_preparations_use_case.dart": "ea79e83c9949f1b86758f6bec6fca52446eb021a7d3eb2987c211bd7fc0beaec", + "lib/domain/use-cases/stream_user_use_case.dart": "1f2dcb075f1a33024e2720941ea19cc886b29932cb4f2664793a69c743c9b934", + "lib/domain/use-cases/update_default_preparation_use_case.dart": "f82e82a391bdb3bd956ecedba14e2b0d00e01848b1b5aac5d3828c50171fab41", + "lib/domain/use-cases/update_preparation_by_schedule_id_use_case.dart": "364f367b7b167b03d39b181724a3116e48e369910750e3f2aa7c54ce1e9e4496", + "lib/domain/use-cases/update_preparation_template_use_case.dart": "022bb5f4cbd1798f30157545d32d695d79c444add58595fa1ab5bdd37fb68d17", + "lib/domain/use-cases/update_schedule_form_submission_use_case.dart": "d31402335f2cfac0b09757b37b547755b45c184fa5d97acec9e72dd2595bb1e7", + "lib/domain/use-cases/update_schedule_use_case.dart": "de3541d03299b4da5efd816161ca8b06442ed23ae416f95fbe6ccb0cd7b0a8a7", + "lib/domain/use-cases/update_spare_time_use_case.dart": "85ee4388108d79f5973b335e39926465ef68cbee939cd54f292d2f27af269878", + "lib/l10n/app_en.arb": "5623ec0393ecdab052b9051210aa2659ab19f799ac846a5a41ea571ca7d45e0f", + "lib/l10n/app_ko.arb": "c5a9bb8e4aa763d6c582071c567d520dce352df7070051ea4c6c82044b78c9b8", + "lib/l10n/app_localizations.dart": "14ac0b3e2383a8eb135ef3c61103290770712482315e72cf8443fed83e7b2f43", + "lib/l10n/app_localizations_en.dart": "db2359e1c11e2ff5d27e7842baf0f78f073cefc15ccc41240020a3d6b2256136", + "lib/l10n/app_localizations_ko.dart": "80f6390b5a06577a13c9a3eb6990d5351a891acc4da8fddec58b6e3199a3d757", + "lib/main.dart": "a198ca90f4b90620aee9e958d609bc463433b10a0577bea46439c20fcdaf3708", + "lib/presentation/alarm/components/alarm_graph_animator.dart": "ed37baa30ed39898e1424c832875557010b6f1dc4051f9d82b34ff8777aa7e9f", + "lib/presentation/alarm/components/alarm_graph_component.dart": "5bdc32f010e7da7aa312654eefed20cc871499bf3c5241c9d582f86086b636b5", + "lib/presentation/alarm/components/alarm_screen_bottom_section.dart": "4353f1ff97338f6b6577aae5dbc2b23bb411f0dd673a493c38395c4e05831ee6", + "lib/presentation/alarm/components/alarm_screen_top_section.dart": "c0333b56f8e9a4f3985919c00e7e729582896b5fb30fcaf6125384b7a25419ca", + "lib/presentation/alarm/components/preparation_completion_dialog.dart": "557159640456ba18b1b7b6ba6df8dd983c8cbd8e354bf4c1d55f23f7db5c4a1d", + "lib/presentation/alarm/components/preparation_step_list_widget.dart": "06e661e82b20f5f57d497140547c1fc6d0a4cedfe56c0622bfc9519bcdefe363", + "lib/presentation/alarm/components/preparation_step_tile.dart": "d636bf87c75551ad884e793640f9c42d68cedeb096075d04ef178ee6963c9431", + "lib/presentation/alarm/screens/alarm_screen.dart": "71cc57c6a8b6c8e58b87bcd517767b7417aba0986208169262123ed53805a00c", + "lib/presentation/alarm/screens/notification_schedule_start_route.dart": "ff9ab41056b411b6c706dd123755db25892e175f12480a4d0a89a8f0d2f5f4a0", + "lib/presentation/alarm/screens/schedule_start_screen.dart": "7ec26038dbf056b8a0a141e838a5a3f253bccb294691b2255fc4be9af18d91c1", + "lib/presentation/alarm/utils/preparation_step_state_mapper.dart": "2cea460d21133d1958bcde6c215a87c09c1899fdedc17e053d876218cbe88c7d", + "lib/presentation/alarm_allow/screens/alarm_allow_screen.dart": "8cef5ba104046b1bcf1d6071387ad27c8f8bebf080e6f44d20a9071723e0fd1a", + "lib/presentation/app/bloc/auth/auth_bloc.dart": "b8b6787ca4e4cc4ad4b0dd0589ccaab80625361813a825c82d1826aac79a3eed", + "lib/presentation/app/bloc/auth/auth_event.dart": "c6c69f06aff8238d9678011b6244ef865b5128c0191303ae3a578e7f4f4da652", + "lib/presentation/app/bloc/auth/auth_state.dart": "66910b6b7e005f18149adbbd6e27353065243bbec8670ac13f22e4e1ecfca712", + "lib/presentation/app/bloc/schedule/schedule_bloc.dart": "61f6f02f7aab6474f6da562bd507a7b8721bef4eb6fb50f342e5a126330f03b5", + "lib/presentation/app/bloc/schedule/schedule_event.dart": "c89b86b95cc1d6aed6651d9393f8fa161b7bd7da2525933f19d68b13bc1b4015", + "lib/presentation/app/bloc/schedule/schedule_state.dart": "b47c54b05837333dfaef224f6c9f448a64708aded92ec50105d73e809630134e", + "lib/presentation/app/cubit/alarm_gate_cubit.dart": "2e97460db5f79359ae97dd6fd85e1fa2dfccc2e493e72c66a3766898444cdbbd", + "lib/presentation/app/cubit/alarm_gate_state.dart": "e130cf727cd7b443992d6c3752ec023f1fb069ae1a305ed164ea201d4eeea742", + "lib/presentation/app/cubit/notification_gate_cubit.dart": "e4800ce38e9f941db23b4e9b2003fdf17f9cd5650d6d9099b52abc40cbcd9f47", + "lib/presentation/app/cubit/notification_gate_state.dart": "240dc7c42dac98b8ff25ba7a00d4bcda49a585c18d4b27d2fe5ed488fc31a716", + "lib/presentation/app/screens/app.dart": "a70e26a65959f877d747240ec6dfc56508ea787fdab5d786eab4f017dab834ac", + "lib/presentation/calendar/bloc/monthly_schedules_bloc.dart": "721b62e172f197dc4b34b21fe037a1c312b315035d1f3dfbb7793e82cf575028", + "lib/presentation/calendar/bloc/monthly_schedules_event.dart": "50f24adcbc94ba49081783c8097aa5ece8790ac3bbbd57089c1be1b9d2dcd437", + "lib/presentation/calendar/bloc/monthly_schedules_state.dart": "c411a2b7d5d1ec82f540be0876dcb44df59748e81cfbd74562d1a8cfad49dd62", + "lib/presentation/calendar/component/schedule_detail.dart": "6429e1d62bf1963ff3bee747347331ec822dc2525b6ad2b1b3a7a991fabd6bc3", + "lib/presentation/calendar/screens/calendar_screen.dart": "d9b4a1f944dd1b0cbb71ec152597816ed4b463682de9ac48a91ae264477cfa65", + "lib/presentation/early_late/bloc/early_late_screen_bloc.dart": "f996d7aeeb3268a290c76c18046ff4b83233e0711e9387349c9e5a661707e337", + "lib/presentation/early_late/bloc/early_late_screen_event.dart": "a9f7b39a26ae682e5f6cc595facd88c11fc263492a1e6befeb048548312f86c0", + "lib/presentation/early_late/bloc/early_late_screen_state.dart": "7d96e1592e45e9cb3fa53f64968702dcd0dcec5a4406f9df9b7cbdacde0b6a2c", + "lib/presentation/early_late/components/check_list_box_widget.dart": "17c48dd49b64463daa0e0a9236944e06719411cf4b0f5a39b0e4c36fa586c2b3", + "lib/presentation/early_late/components/check_list_item_widget.dart": "46725440280e9536843a90b3438e016737fbbad9f295d758b0c0aa926f38a3c5", + "lib/presentation/early_late/components/early_late_message_image_widget.dart": "9909c37278b758bd8a5178e5ff50fbc2afb905ad1a7d213137c063ec5160e26e", + "lib/presentation/early_late/screens/early_late_screen.dart": "67d348472a5b1e1ff17bd677736e6d8f27c1029d6f1b63aedc2418cda69431aa", + "lib/presentation/home/bloc/schedule_timer_bloc.dart": "1e143ce9312246ef58f447bcd6687d6408c8146d7513bbc2c42522df19b55b03", + "lib/presentation/home/bloc/schedule_timer_event.dart": "2012a71a141e36eb0e21ff8f540de463f0707e1fa4bf48de6923e068b85d1979", + "lib/presentation/home/bloc/schedule_timer_state.dart": "8b07c64c5ec929bd977fef9637b6017c8a57b4d3ba0414793b7826bc3061e9b9", + "lib/presentation/home/bloc/weekly_schedules_bloc.dart": "a2608f31c29f0e158a94245f61baeb27adceb7500e251f5d807ec74e6d18f256", + "lib/presentation/home/bloc/weekly_schedules_event.dart": "a5f5805f1e88fa7f2121b75dad4beca4dceb992b1e4c117e66389e72bd6d17a3", + "lib/presentation/home/bloc/weekly_schedules_state.dart": "8bcc3b15e0339e05eec7ce8ad3fa1b4de1c06f94582b18760424c309ad590fed", + "lib/presentation/home/components/home_app_bar.dart": "1f208919fa9a0ad892e9f2cce92123d09435635b2a1f44f9db1262677da4e07c", + "lib/presentation/home/components/month_calendar.dart": "8e944ad703a04755dadc7be685ff0fd1088e8a62bebff781dd9b1acb1b9a3196", + "lib/presentation/home/components/todays_schedule_tile.dart": "8755c31a910bca8c3249ca9f54139b047492463987eb6bc47bfdaec47def0be7", + "lib/presentation/home/components/week_calendar.dart": "15cf725794b45a5d229029eb160ecb3f9cd9bd014a850aef34d0f0abaaab55fd", + "lib/presentation/home/screens/home_screen.dart": "d105d27fea2395aae1392b68058ed82a141bba07823bb392cdb405cc97ea694f", + "lib/presentation/home/screens/home_screen_tmp.dart": "7ee72ae05a14fc7fba987c1a675d9b90686635d2952475ea47edabbc764d97ff", + "lib/presentation/home/utils/today_tile_navigation.dart": "6f459e298de9724876c6c38e4a2335e37312db13d548cddd26bf14cf44ab41b4", + "lib/presentation/moving/screens/moving_screen.dart": "afbe4ffced63b31335a76fc1e7698c63989e5cd803050f6ffe29f76fc8ef7b52", + "lib/presentation/my_page/my_data_screen.dart": "999189475c862367ecd6ec843a5b9937fc56f474d5b2367b99dc04cb079eb6b0", + "lib/presentation/my_page/my_page_screen.dart": "4ca549582472ae6ce460fd27681ba1505e5a8d042c03a89ed61b600027f0bf37", + "lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_bloc.dart": "a21d1c4fd924c46edce019000654ea5c05342da7fa1940f010ce2bf4f0911827", + "lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_event.dart": "f5ae1797493041481a48147b50f058e568c6d0c470279e070c39beed38543741", + "lib/presentation/my_page/preparation_spare_time_edit/bloc/default_preparation_spare_time_form_state.dart": "d344c22e862afe5411a008bc9dd32ac8ed1cc4bcf090dc098b69b554b7076c35", + "lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart": "01251c324ee7d6321ae48afc03dde4a3c5972051e169c737d4198cd1d744a2da", + "lib/presentation/my_page/privacy_policy_screen.dart": "098574b97aa8b196e59228659cc21ad42c2b21469bbb0eb0b6c63d628c590082", + "lib/presentation/notification_allow/screens/notification_allow_screen.dart": "98be51d9cdb0f5129aa461522d830b69db99d1aa1cac531fcb6b60bfd90caa8b", + "lib/presentation/onboarding/components/onboarding_page_view_layout.dart": "70cd236da27d4067462ccf8789afbccf5102b890bfb7e0c89744310b625cf44b", + "lib/presentation/onboarding/components/onboarding_title.dart": "9ff1b70ecb872b5f98cfeccf8179f47ab3a47a323654bdcb080db6cc49bb22d6", + "lib/presentation/onboarding/cubit/onboarding_cubit.dart": "9f3bdae18dd3c0509a99a9e772dd46e79cbc0d4eb434f26d0ed85de3d41a8623", + "lib/presentation/onboarding/cubit/onboarding_state.dart": "1d8e9044020d1f7eb8c39f19ad8b84b725d35664f90fff633f9de67d75aa55cf", + "lib/presentation/onboarding/preparation_name_select/components/create_icon_button.dart": "49ff8c85d7c9b0c739250b87ff77485e640ec1ce7b3222969089061d439cfb1c", + "lib/presentation/onboarding/preparation_name_select/components/preparation_create_list.dart": "017513d12028e1cef25ea2f2bf93ca91914325fe8b65a8c01e7118ef24491ea8", + "lib/presentation/onboarding/preparation_name_select/components/preparation_name_select_field.dart": "693a9f66daaf3ecc851a8288c16b1a61d530581ebf5a28ce3ed174f1c3501431", + "lib/presentation/onboarding/preparation_name_select/components/preparation_select_list.dart": "2c0454ddd0c5fa78ea57f19f836061f8c2add2ed0e166f834c3306220231c765", + "lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_cubit.dart": "e46d2063d89d7ed77f370f871e62d504ed9da0386e5b52be0860b843aa452334", + "lib/presentation/onboarding/preparation_name_select/cubit/preparation_name/preparation_name_state.dart": "9afd6432f38beabb80498b0a09550f04cbada24b34696f70229d209e8b26c024", + "lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_cubit.dart": "f8593376df00bc228a3307c223b9e8e43e11c2384233e3124c98aa906b17c466", + "lib/presentation/onboarding/preparation_name_select/cubit/preparation_step_name/preparation_step_name_state.dart": "ff1dbfe8f78f12ae5c747dadd4295f4b1550d461838f051477a1dfc256c1c6dc", + "lib/presentation/onboarding/preparation_name_select/input_models/preparation_name_input_model.dart": "5014ec0d0112b7672e96d81df431fa0bc125f2dd087d1c347917e55053c9350d", + "lib/presentation/onboarding/preparation_name_select/screens/preparation_name_form.dart": "a3f5976ad86345c05835f8aaf8c669c7dea02fcc241fc3bc84e494da94137b37", + "lib/presentation/onboarding/preparation_order/components/preparation_reorderable_list.dart": "8c8793d70791e923403062f1079cef08234bd2364a1548c2ffaefff85392dd69", + "lib/presentation/onboarding/preparation_order/components/reorderable_tile.dart": "218bebcffa99eb3e69a0270fb1dcf9dc86c5a734c89fc106e2e2289a4dc5a74f", + "lib/presentation/onboarding/preparation_order/cubit/preparation_order_cubit.dart": "3208a4cf3fbb86473768d0f2299005b7001e83123533f06d7fa8abe65eb8ea9b", + "lib/presentation/onboarding/preparation_order/cubit/preparation_order_state.dart": "6f5009747fad8332010fed6f950ab7f965b9ac9a01cb50eda2a9ffcd9d0b0c56", + "lib/presentation/onboarding/preparation_order/screens/preparation_order_form.dart": "8d4717267f08c54809ba8ae3dbc49448a1726e77e37fd0a4c0a1807ff23a3332", + "lib/presentation/onboarding/preparation_time/components/preparation_time_input_list.dart": "aaaed48d4f8b5b3929ef988ec8b5c691c7007ca0d867dd2bd23ad9c68f2bf573", + "lib/presentation/onboarding/preparation_time/components/preparation_time_tile.dart": "b1c2713ffbfb51cde46bbdc4369c30a81217394a2538ffd195a5b7fee0a97c50", + "lib/presentation/onboarding/preparation_time/cubit/preparation_time_cubit.dart": "974fc861181ce220bd0e41596ac73c99cc720580f9fe9da8fa8560160c8c71ec", + "lib/presentation/onboarding/preparation_time/cubit/preparation_time_state.dart": "7cb4435944e644447a29e23fa23d27a79b98629a17512322c7a4453563ae679a", + "lib/presentation/onboarding/preparation_time/input_models/preparation_time_input_model.dart": "eadd4ff20ef7dae4538c434b3144c8dc21703f15623259ed5d018fdcb4daae80", + "lib/presentation/onboarding/preparation_time/screens/preparation_time_form.dart": "aee3dac620c9ecca0e57f4fdfb759a846a55ed2d8edc1fcd3a408685fb369a08", + "lib/presentation/onboarding/schedule_spare_time/components/shcedule_spare_time_field.dart": "8a4ac813a36584390d9a42cb6e81e9760a59889198c42a76b440a1b36f743d64", + "lib/presentation/onboarding/schedule_spare_time/cubit/schedule_spare_time_cubit.dart": "ed89ccea55c48e7e5af60d831afbe5e84baad43ad579ca19cb443548fb731564", + "lib/presentation/onboarding/schedule_spare_time/cubit/schedule_spare_time_state.dart": "3a76b4a8cf4d70c7fe6a9f3971579b8f76badc85a68e3716fff0be1ed4ce8d48", + "lib/presentation/onboarding/schedule_spare_time/screens/schedule_spare_time_form.dart": "16d812f5d590590b69ce0bc72ea0afd1a20f0c0e87b1483c28739b7b829fdeb0", + "lib/presentation/onboarding/screens/onboarding_screen.dart": "ea2df70d24d5cc32cc5567a93471e8a5a10e3605afddc37f70bfaaed34293759", + "lib/presentation/onboarding/screens/onboarding_start_screen.dart": "1509ce972f5de6b33449d6defeecfad036d9b62bd4c6e076bc01e6358b5c8439", + "lib/presentation/recurring/recurrence_components.dart": "71307f4462359f9dccda825b6b2de2f6bf2cfe671123faf6d625937af9a17855", + "lib/presentation/recurring/recurrence_labels.dart": "7169303c215d0eb6bf80f644162a8f94161b07976d9dbce7a67933608e0549ba", + "lib/presentation/recurring/recurrence_review_sheet.dart": "4d0159f35013102213fdebda12d70251b3e924c6b3979a5cc8609772efd445b8", + "lib/presentation/recurring/recurrence_scope_sheet.dart": "7be2811acf84a19249f9880bde2912695610b15441e3482920437a5363a45fbf", + "lib/presentation/recurring/recurrence_settings_sheet.dart": "7ae41651e68a610d356ef53b2a915110b25bae38443e0519db786a14334929f0", + "lib/presentation/recurring/recurring_management_screen.dart": "e7a538892cb38f2933164286a4b793105b2fe0dc0dda536a76589259239c30a2", + "lib/presentation/schedule_create/bloc/schedule_form_bloc.dart": "0ceb6482106c08fc14eed50ec081e9ad9a9659f63b188c60d50139c3b76163d8", + "lib/presentation/schedule_create/bloc/schedule_form_event.dart": "ee446fa5d3f97ccf8e4d27d3488b83d430a559c63b96567ef21fbc510c85ceb4", + "lib/presentation/schedule_create/bloc/schedule_form_state.dart": "d7df8c262bf079997494945c3331b59efdbf030f853ba65547846f5cc173c88a", + "lib/presentation/schedule_create/components/keyboard_backed_bottom_sheet.dart": "c5284ec29224d69c611b912d74d440c447769e1e1b73c9fabb2ab45ba7f607e9", + "lib/presentation/schedule_create/components/message_bubble.dart": "29b6f42b0ea61d7200029a0c619918e276df1269ecc51e1f3a6efa3434fef254", + "lib/presentation/schedule_create/components/preparation_reorderable_list_form_field.dart": "ef91f5f5aa5c6948794d9893381abd10cab61a6a55ece0ba4e11cfb4f342adb8", + "lib/presentation/schedule_create/components/schedule_multi_page_form.dart": "8bc43ec0a3f466b5883a6cc61cb1156ffc3b95a9b379481b1d311b4856ebd5af", + "lib/presentation/schedule_create/components/top_bar.dart": "16448b37049ab0ecd84b412379548f8f746848793fbd3532320493bb1d11dd06", + "lib/presentation/schedule_create/schedule_date_time/cubit/schedule_date_time_cubit.dart": "73d7dedb49c04434c9359be12c8244216b15829096df90b59f05d77e97fb05f5", + "lib/presentation/schedule_create/schedule_date_time/cubit/schedule_date_time_state.dart": "9c145835139e857f4f5ce5953192526f701325e7efbbaf3504f779ab2a279e96", + "lib/presentation/schedule_create/schedule_date_time/input_models/schedule_date_input_model.dart": "73532c71fe4abdb4950b12254c6f734d5c3bc5866c6422c5856d716174678e9d", + "lib/presentation/schedule_create/schedule_date_time/input_models/schedule_time_input_model.dart": "c4bd8c1107db2166659a3fcc6c8596e4bfbb7aada26f383558e5ad39000f80dd", + "lib/presentation/schedule_create/schedule_date_time/screens/schedule_date_time_form.dart": "5ea75eb74a5843f6bc4dc942ebcd37645238cc41fa73c64c992d40a000757c16", + "lib/presentation/schedule_create/schedule_name/cubit/schedule_name_cubit.dart": "627a33307a7e2d5f100946cc096ad317057e3faccc2c243843c916824abae760", + "lib/presentation/schedule_create/schedule_name/cubit/schedule_name_state.dart": "7306754d3e5fb1e226c0491a7b9d5e4d47e668e26600d610c02fcc55b3433f4a", + "lib/presentation/schedule_create/schedule_name/input_models/schedule_name_input_model.dart": "af715181e54123cdf8d962e81a0dbc19e203ed6a2eae19a987bf9d657a45fa1d", + "lib/presentation/schedule_create/schedule_name/screens/schedule_name_form.dart": "098aa18942746143a97fd3ba1e1a75c85c12bfc118c0c4411d5b23ee975a82e8", + "lib/presentation/schedule_create/schedule_place_moving_time/cubit/schedule_place_moving_time_cubit.dart": "ff1fe81c0dc5fdaabf9ceee8bf304bf29793dd91538917332694c964e3d2fdfd", + "lib/presentation/schedule_create/schedule_place_moving_time/cubit/schedule_place_moving_time_state.dart": "1c7d6f439a9d39bea96de9bc2ea7488c589d92eecd7c49a19198aad88983df88", + "lib/presentation/schedule_create/schedule_place_moving_time/input_models/schedule_moving_time_input_model.dart": "888828ae77625ffad9740e8b29d40167fa8fbb2c1a573f4ff16eb2e0ed0dc93f", + "lib/presentation/schedule_create/schedule_place_moving_time/input_models/schedule_place_input_model.dart": "8c84fc1bd40deb193dd0e98975b25abf3b94edaf1576e44aeee319f0a3464049", + "lib/presentation/schedule_create/schedule_place_moving_time/screens/schedule_place_moving_time_form.dart": "370a6cb278cd5afa551e3517718272a862d19f599143fdb1c0be8880c7b52eae", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/cubit/schedule_form_spare_time_cubit.dart": "4fd8e64f397ef91ae4ecc0a24365d71ced4c3f136d171dc88665665be2af7ba6", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/cubit/schedule_form_spare_time_state.dart": "d1e221e3814ab31964c149d87adbbfd2975dcd29c7dca991b595c793b59d2d18", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/input_models/schedule_spare_time_input_model.dart": "a7a84cf2b66d963a0b99af4f2539923227b3417a3a59389bbb9faf85478abac7", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_bloc.dart": "7a4bfb1dd8f5b319a3c0594b88ea59a4e41d8169741175099798b98e108b5f63", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_event.dart": "a8ce756a34b5b6ce4c55542678995debd3e0401bec8ba90b554666163812d805", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_state.dart": "b1f1cd3d17f2f49902882e91b13dead14a5239ca6c5f5adeffb6398aed256b05", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_create_list.dart": "108c4fbf61c9ce11b053a41107b473a359518182bd05d3a5181e9cdae1052d5b", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_list_field.dart": "c02a2814133c42075a8e435935b38bfe6a6a6219771d79d705efc9ebb0da21ab", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_form_reorderable_list.dart": "91480ec26c26da89e6765257c6323e20b252dcf05d6968e1fecf4825aca8024a", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/components/preparation_time_input.dart": "0562e6cb4923a575f155f6668c7098b953e1563b7502d30bf3289fb5637b4025", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/cubit/preparation_edit_draft_cubit.dart": "aaf683923ff0f89a33b4f0b218bef78f2ff450c2b6bcc725fb0f0d41815026d6", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/cubit/preparation_step_form_cubit.dart": "33e2f996666004d99a04fa7ac793f0024388f7ac99442f74c2b4c69b1e9f16fe", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/cubit/preparation_step_form_state.dart": "bd7ab5172c6d53847694483644e48dfa663ab026dcab02ee6d30653c4673dcb5", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/screens/preparation_edit_form.dart": "87ef529118a7957f028426128361f2098656e9c7b7d84fbea17e6229614b6098", + "lib/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form.dart": "933a38c19757ba7e59df68d60d36453d341ec402eb7a39f01c918f29865b8cb3", + "lib/presentation/schedule_create/screens/schedule_create_screen.dart": "2db8bb7aaaa5b8f99221f1b700cf76e68419bc20f1c799e0b016a7e4d8f0a178", + "lib/presentation/schedule_create/screens/schedule_edit_screen.dart": "cd24984ad947e730b0e70c8eacb45c7b469c26413ece5ec588e52eb21945f9a4", + "lib/presentation/shared/components/arc_indicator.dart": "ff2cdce39136e456503d4dfe4fc346f5e5898ccd03b0b4d6899c9384fbefdcba", + "lib/presentation/shared/components/bottom_nav_bar_scaffold.dart": "9273a357ac931802a4e3175f752614477852fed0e120e500ce29cf88483ab052", + "lib/presentation/shared/components/bottom_navigation_bar.dart": "431ccb3643756b89a8bff46277ebdeda6c0d1d32fd273884a92076ff4c05bd00", + "lib/presentation/shared/components/calendar/centered_calendar_header.dart": "65ff57550fd149fc24d918c388d1df021172b89279f0e7cea5e889e4d05f0a78", + "lib/presentation/shared/components/calendar/schedule_marker_builder.dart": "af298fd331c94a632d23baf2875a16d0e4597318542bf36a346e8d16c68aba01", + "lib/presentation/shared/components/check_button.dart": "1bdf31d714bcb58a05f89f892297eda954898d53a35ea906294f3c02588822de", + "lib/presentation/shared/components/cupertino_picker_modal.dart": "fd0168081a9e39a75034ce4bc1ef970845b6a9e7aeadbc8110f652b535b1a8b8", + "lib/presentation/shared/components/custom_alert_dialog.dart": "230968acf50a104b8ff93e338e838b09d74aca8a0ce70a5effa7ef0275a339f8", + "lib/presentation/shared/components/error_message_bubble.dart": "6e0014a4053f1be61b6eeffec795630eea8e85730141b141ea48d74c62a5b3c2", + "lib/presentation/shared/components/loading_screen.dart": "cfc1e7a30077bbf7aa24392abb06d05f1ee6a51d72de682b66f861bcfbe716dd", + "lib/presentation/shared/components/modal_wide_button.dart": "3299ccf25b8268cf5796f39345b36ce4126aaf4a9efdb53a24c248021c5cf3bf", + "lib/presentation/shared/components/notification_timing_education.dart": "bdfca87e3ba18234dfc41d7cb29ce4af6b1fe7c76828ac98aaf7efd7e4bd4207", + "lib/presentation/shared/components/step_progress.dart": "dd6de49d30b50652fcf759f8aabbd223ea0602c3fd14060146240a9e832e98ee", + "lib/presentation/shared/components/tile.dart": "612f63b597ee5c12d76ea182f0bf397f9b6df0fe8594218099ac70d3cfdd0ef3", + "lib/presentation/shared/components/time_stepper.dart": "f2f6bc1971d4ffad5bc92b4871599dc00b76cb97e103a950960514efeb84f615", + "lib/presentation/shared/components/two_action_dialog.dart": "d78a54e7cfe969e40939b740a9ba60dd3aef35c0b8202d97601ccefb57854095", + "lib/presentation/shared/components/two_button_delete_dialog.dart": "238b4218f13a91c9409f262b4682f59824360a6fd475e2d34db2329f69ef0acc", + "lib/presentation/shared/constants/app_colors.dart": "dca7bf8e7b1a74b93ca87ac17635c6c0c5a731c41116884977539e20e2e78ef9", + "lib/presentation/shared/constants/constants.dart": "2b0b1a2d9130f201a675af89904a71ded3c056e70e24a557b825709c9165271b", + "lib/presentation/shared/constants/early_late_text_images.dart": "373b101f04cb658cdda21574dba94ab7b98d94eb0349c1e61eafa39ed6e5e13f", + "lib/presentation/shared/router/app_route_transition.dart": "b727d9335c01475dfb8d3016c7dea72bf55906b701607feb00b800b0ae2d3212", + "lib/presentation/shared/router/go_router.dart": "0f7de563084536cb573dfe3cfc4e84d00b009ac7a735753024d10bc51e2c7582", + "lib/presentation/shared/router/route_arguments.dart": "f97531dad02c929ade1aa73a8a6df1eaa8a24313018b517ac0277f6ea32073ec", + "lib/presentation/shared/theme/button_styles.dart": "58f8fea4d7a728f387ced58b594d97db036774528f61509719f86a30bddcbe0c", + "lib/presentation/shared/theme/calendar_theme.dart": "33c85328ad77666dfdfa42dcc033dd2adfd06bcc4e3185881a03c0b7b576492f", + "lib/presentation/shared/theme/color_scheme.dart": "beffaaa6aacada4e982d1375aa87c9871a6d0201663be2831903bb5aa03eeaee", + "lib/presentation/shared/theme/input_decoration_theme.dart": "cb571d5c7f0dca6f696f8227b5738c392a369b092853b8a05562e6989fdae193", + "lib/presentation/shared/theme/text_theme.dart": "0999af756973ba3af517333a7ba736bb92cc92ad1f687874467dde74f3e4ea65", + "lib/presentation/shared/theme/theme.dart": "abfe61d784665ae144e360a97aa811511fc6950f69335cd3bc2d3403f36571df", + "lib/presentation/shared/theme/tile_style.dart": "6d163a250d69adf3e0d340d9184daa1d65d9a789d11b46adff0a9430794bd686", + "lib/presentation/shared/utils/duration_format.dart": "f8812701bda47d580dfaff36f4b790494ddcffd41a8893dfcabcdbe647b8103c", + "lib/presentation/shared/utils/login_platform.dart": "07ecf92084bfd790c0266c576d95bfec66de4712becb949b69fb070f0458cff3", + "lib/presentation/shared/utils/stream_to_listenable.dart": "daf2bebb059f45dd57511a3fa57ced28d70ae53348924fefc0bf89e9dccf41d3", + "lib/presentation/shared/utils/time_format.dart": "83a824385d87fedd9d59d3d644fb6286803b8121fbe8d8e2d61041d1294573e1", + "lib/presentation/startup/screens/local_data_recovery_screen.dart": "8aa7181d9125226bd970aa60a86bed61434a9f24bf0713af50919b696157f3a9", + "lib/presentation/startup/screens/local_reset_progress_screen.dart": "22f38e6e93a9b07742a49926977acac4532973ad20abd6203137252550dad8f8", + "lib/presentation/startup/screens/local_startup_gate.dart": "e962c2e30c15b5cec160d7ff63b6996b0eecdaa3a552efa5675521fd55b2d213", + "lib/presentation/startup/screens/startup_screen.dart": "7a8475e5edb9be5e557f0b6f9191d2b023d60857fd3c23460c1bce73a9f72af2", + "pubspec.lock": "097b9cb731bf8e46e73ea9aa907f223dce7ec5432f9e45ccc69347d5aab1cdda", + "pubspec.yaml": "e9948b9c20fd0f3937d3b755959f0642ff065370049d280a6aec096379d40d3a", + "test/core/backup/backup_crypto_test.dart": "919b76c76e3a981bd6f07fea477e328e940c8e542b2488e8453f78abc819d023", + "test/core/backup/backup_file_export_port_test.dart": "246517f0eb254b5fd247cb0db94aef265bbf18286f2b9ec412c773347074069c", + "test/core/backup/backup_password_test.dart": "d4f2b255bf8cab46994ecfaf24e4f639de21e474b3bfea486459e4650caf0108", + "test/core/backup/backup_restore_picker_test.dart": "e1e4fd9bf75d61be96a9bbb0dbc52ad449f7a310c5e48d6386baa669086261f0", + "test/core/backup/backup_service_test.dart": "a414c730497e3459e615f0c56b1db2232c463055b6a52df80e9cd2fb4ecc7547", + "test/core/database/alarm_journal_recovery_test.dart": "d4861dbaea522c2811872172a51df80d8a4316d7519f9d2acc6699f5898c5c09", + "test/core/database/alarm_replacement_integration_test.dart": "717f0ae18f82d75228e7c88a2bb9d644d3cbbad5ddca3a974bf5c4bc332d3473", + "test/core/database/bootstrap_privacy_boundary_test.dart": "3a7246a1a4ff4042c1d31fc5fc60545885fb5ae3c1da833c32eff9b3c1a03847", + "test/core/database/database_index_test.dart": "ee5477d54c6e516f9d4d2581db4e390b195fd5fb06ab9f94af78326a04141ed9", + "test/core/database/local_data_operation_gate_test.dart": "2f16b5f4f26412c26d6cd7c5d0f0688d1f5a6338f0257c3050bfbe3aaf19e1f9", + "test/core/database/local_reset_actions_test.dart": "5a22465c29a3bd96fecce055ae5aba721eec504833a0962bfc71a24f413071d0", + "test/core/database/local_reset_protocol_test.dart": "4d5f256dbb29dba29fc6b79b0da6bdd4c7024e2edfdbe19be9cee18fe465a99b", + "test/core/database/recurring_migration_test.dart": "3f7b644ac4499d68ecaaa41a7204f03565c87650b27a710982b60397e2f434aa", + "test/core/logging/app_logger_test.dart": "342b817a203e2c4e75326da5bdac5155c42a3fb9494c2c28fa40e23f24656a9c", + "test/core/services/alarm_ownership_journal_test.dart": "c905fdb82a0630994568acf3ffab03d94bd2dbd304946f6a563a45784086d94b", + "test/core/services/alarm_scheduler_service_test.dart": "831ae0495eb26067f5f77e863fde002d269fe01e1d5f2f773892f60fe5741ef0", + "test/core/services/android_notification_timing_test.dart": "f2e9032bf1be099fbf36d8ddf1a6bc29d0763ebd7de3ee2c1bed4f7d086ed168", + "test/core/services/app_metadata_service_test.dart": "fb0c5dde9142829d5a3ffd8df74f766a3de9317ba0dc1c7d6f6928a6ddb427ad", + "test/core/services/delivery_observation_test.dart": "c249f8c99a5be63d831db724418f8dbc6c4bbdc518815afcb840445db2412e09", + "test/core/services/fallback_alarm_notification_service_test.dart": "481b16728461a7eb8bd2fc9c470fac09d612f50da18e6799dc180843bf744381", + "test/core/services/notification_content_test.dart": "800b7a336f82befc9d9c11f7c4e107fd71c23b6c86353f0d9c30af7207c6ddc0", + "test/core/services/notification_launch_test.dart": "a8c8eea53276b67aaf6060dccb952dc112631f19b1ee96bc9630bb5208225cbe", + "test/core/services/notification_privacy_cleanup_test.dart": "31c0a8a40125671588d71cbadcc039a4e6cf5f6ea87539d4259f0a09fd6121a1", + "test/core/services/notification_routing_test.dart": "71cf38246d05be90e1322d6a94b6a7445028f837eb7fb48ff061a929ac07008d", + "test/core/services/notification_tap_router_test.dart": "7718c357f5433d455a6495798b4f255ec2a305f380ee7cd27871e4e5e34d5e6d", + "test/core/services/runtime_privacy_migration_test.dart": "2e106d058b8b33386006794bc46a05fe497f87fd8e416eb6156ac50482abe4b5", + "test/core/services/scheduled_notification_delivery_test.dart": "21e4d57360f69211ce2b0ee98775a457f107f452bdd6fa42c3c683ade0f5440c", + "test/core/time/civil_time_resolver_test.dart": "9c3e0d7c41615b81aa71b4e7925fd052d1cf23af9311c82e065f0f18c6c1f21f", + "test/data/daos/preparation_schedule_dao_test.dart": "9f613abafb9c971d62057747b2dbe9d8591d92f90d06928d5f63a53006c723ca", + "test/data/daos/preparation_user_dao_test.dart": "cb2ce4d5bf10dc4b42e05e1d45806cf7a90e8140cbef8b14e35fec141987753c", + "test/data/daos/schedule_dao_test.dart": "1156eadf8b049b765de89acf07e9749f000011c1b48ec28d153293f4eb1407a6", + "test/data/daos/user_dao_test.dart": "c2ad0692c4563e65ac6881c6cf7bf8a7e1dd3af216ce3b5001168d40c9d6c88b", + "test/data/data_sources/alarm_registry_local_data_source_test.dart": "e3f349245629404fbca999c18724177f70acf07e4f0bd30e23c60b0f3a30f772", + "test/data/data_sources/early_start_session_local_data_source_test.dart": "eff79d3b94bf9a1c368a24ef35272bcb9487e2d16c9e90496e7b6b8c12060907", + "test/data/data_sources/preparation_local_data_source_test.dart": "d747510a56dec3a8557ea69649c74b8a6f593d02921afe54c918a4391ea238c8", + "test/data/data_sources/preparation_with_time_local_data_source_test.dart": "15031e33843abc333409884ebd25122455766e6cbd6cd991df7488580006d7f3", + "test/data/data_sources/schedule_data_source_contract_test.dart": "e25dffb2fd359ca69c420a326a021c56c3d884516a0ad9d36047f8664231d41a", + "test/data/data_sources/schedule_local_data_source_test.dart": "a8856ad5c1ccc1cd187b8b66bdd1d65165f2805ad4a28630aff6b9b47a29c106", + "test/data/mappers/domain_persistence_mappers_test.dart": "b82d1a16fddfa78122f2189e97e29034ef1a13fd4df6b2dbcfe947b57fd74f0f", + "test/data/repositories/alarm_registry_repository_impl_test.dart": "0b8060ced5059b4590a836c7eda138da3e4986c47d47b20d3518858f44524360", + "test/data/repositories/early_start_session_repository_impl_test.dart": "822f070d8f7baf793a11199065c9aedbd9817c836b74e150a47541f4e6f0c8e5", + "test/data/repositories/local_schedule_score_test.dart": "8d2ae57c2cc07ccb2dd8e01380887c915bcadf336ca22852a9b4c961ec1b5ad9", + "test/data/repositories/profile_write_preservation_test.dart": "08ea6d5b6f0e84cbb33530d545ed584ce5820a2342760ddc20395a9ee796ed82", + "test/data/repositories/recurring_schedule_repository_test.dart": "0cd661ff7c8083bef92eb109e9c81b56b57a9de9adbb530cad2eb4734f67fd65", + "test/domain/entities/alarm_delivery_policy_test.dart": "10fa2dc88c6600c8e6eac1c4b3065cfc6d0ed22bcf8db413f9b48dd4cb8c0afd", + "test/domain/entities/domain_entity_boundary_test.dart": "339de8a5b78ab55c2450271892f5d72531852d71996868c9e9a9a28f37ac1985", + "test/domain/entities/preparation_snapshot_validation_test.dart": "47a4934dfaa4f37c95a38105acf5ed03efd31962a7a87e2dc56abbd3f4a002de", + "test/domain/entities/preparation_timing_entity_test.dart": "bcca6cbbc4cb9551888e1e851a97eea7d9755f7c83ddedf3400f24b75917faf5", + "test/domain/entities/schedule_notification_status_test.dart": "c47ed772514315e6fc02fb967fa586ea50718007743f336ab1c91de6fce8733b", + "test/domain/entities/user_entity_test.dart": "b46a07ee0ead3ee9dff66d61327d1f07d9fe8cd3a6ff31f653712813fd09c884", + "test/domain/recurrence/recurrence_engine_test.dart": "12d6971f5038aacbc62b8814c7e3a728a71008990a14feb08f9a9f3e0bd9f314", + "test/domain/use-cases/alarm_reconciliation_concurrency_test.dart": "621bfd5c1a14a48c5b39d97ebc37d16aa047ae7ed4be96fecc3b6fc96373b3e0", + "test/domain/use-cases/cancel_alarms_use_cases_test.dart": "4427e8e23f559ef727171345128ba1599b8bc55bf8883eedb0abbaad7316231d", + "test/domain/use-cases/create_schedule_form_submission_use_case_test.dart": "71af83eb6168190a064b7fa2287f37e67eec0a1dd38730c3421d199e3723e6d7", + "test/domain/use-cases/delivery_reconciliation_test.dart": "f9d324dd9dacb1dc195239c038f13d00c59d91c5fca61f58088e835fe7531fb3", + "test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart": "c296a920c456819e7bde116f30e1a8277a6c861b4aac732c493b6eb9af777a19", + "test/domain/use-cases/load_schedule_form_draft_use_case_test.dart": "202f7047efcbb306b36f596111dad70bd1e85f88f1222802a86dae6a219f968b", + "test/domain/use-cases/reconcile_alarms_use_case_test.dart": "37bcabb8fd546e363ad0c84d9c0810a625edc09c2bfebfd893a02046778d4646", + "test/domain/use-cases/recurring_alarm_integration_test.dart": "4b03a28c3f2f66259434d424cccf831eb38f37451f00c23a6a1dbcdbe5493142", + "test/domain/use-cases/schedule_date_use_cases_test.dart": "09343cbbb05c12d3e0ba7a106d15fb8a99b4157e2ce9da4a23bc035646329b16", + "test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart": "b767e76b1bd30c61b370de46f1f4b791224317ca270c2c1fd1580a0ca4af8253", + "test/domain/use-cases/schedule_mutation_use_cases_test.dart": "7cf40b17f32fe892acacd371bccedd513411e59860db3339ecdcd2fb49273676", + "test/domain/use-cases/schedule_preparation_session_use_case_test.dart": "a01103a4a356a0b72356c95d2a469fcd50ad724be17ff901265b77c83af88a08", + "test/domain/use-cases/timed_preparation_use_cases_test.dart": "36f30958c8ccb72d7d999b3fab9ce53b04f5dab3628f837882147cc41aa0719e", + "test/domain/use-cases/update_schedule_form_submission_use_case_test.dart": "e89f0045b7d525f9e80a8203fb95dc2c0ca587c7e43307dda662c860ae668b02", + "test/helpers/isolated_alarm_owner.dart": "84a10eff3d2f4bbfca21e96c8d3e3d40fa47b11feecf0ce885df1c584ee22352", + "test/helpers/noop_alarm_cleanup.dart": "5b26450418c9b461dbf338d4a794a1535d7f4b711d7f14dab261bc67bab00c7a", + "test/helpers/noop_alarm_reconciliation.dart": "dd2a9e0f9888a7c64466daca2b16e78bac3a6a2361a997d69ca7ad6355548fd9", + "test/helpers/sodium_test_loader.dart": "be13982db4d2b5142b809f0ffd5872445a20a2db48c335751ef83f3c2344139d", + "test/local_only_boundary_test.dart": "ac033712134bcef712914e03420ca90f8f362c01178491e4ad86fac6a8932646", + "test/native/AlarmLaunchPayloadTest.kt": "a9ab8a7e1cffe0c291fe0bb8ee671d8f08c98e23bc89baa121ad0a8e16c48e2d", + "test/native/AlarmLaunchPayloadTest.swift": "407f6ea590e7de93ed7c10a46414c2295fa3f2c9bbe0435660eaa70e5a70c576", + "test/presentation/alarm/components/preparation_step_list_widget_test.dart": "c48c593db3e0d83db5a5458151e898ed7857da71ff7217d3a7576b45bbb9a143", + "test/presentation/alarm/screens/preparation_flow_widget_test.dart": "c0d94d4b4e5ebfd7c0a5cfa217993cde1f8582ac5e6a79a19b3e617dfe648c5a", + "test/presentation/alarm/utils/preparation_step_state_mapper_test.dart": "d7cbf6d14e0b22352e025385fbfebf307ed3f496fab86ed58b2394ebd64748f5", + "test/presentation/alarm_allow/alarm_allow_screen_test.dart": "dfd31f5fdc9fd2d295a6b9a015867ed1444cbf44b724dd004de7777e56696810", + "test/presentation/app/bloc/auth/auth_bloc_test.dart": "b95d1caaff25751c97a24af5b920b21de157628db2656e8b52cf69289083e2d0", + "test/presentation/app/bloc/schedule/notification_prompt_race_test.dart": "23bfd5514f3e8fa8b80800aca2c9ef3c0fbf8fd6eede6e72cc0816019ae60977", + "test/presentation/app/bloc/schedule/schedule_bloc_test.dart": "c3900c31b2be670038af04e93b2c7682e8284b3b240722c775e1e39fc9165fd1", + "test/presentation/app/cubit/alarm_gate_cubit_test.dart": "ceaa6032a46b88460ce304c14847904d529e668080de22805535b58169901e53", + "test/presentation/app/cubit/notification_gate_cubit_test.dart": "14a3b1264b1c4c4e0569f9b64667137b7af99d82ff9fee94bc78c63ba4b5fc3b", + "test/presentation/calendar/bloc/monthly_schedules_bloc_test.dart": "bcd8d66694ccc2895badc41146cdd5647633949cddbf44741da25380cb5b7fec", + "test/presentation/calendar/component/schedule_detail_test.dart": "7a19c9fc2551b4b77f1055a6a5b21d80dd005c1fecebd57c330b129afeee38d3", + "test/presentation/calendar/screens/calendar_screen_test.dart": "a41cb38154a0b63de55e79453592052907bae6045973d564902f9cf66011abdc", + "test/presentation/early_late/bloc/early_late_screen_bloc_test.dart": "0988303a4941fe4b27922b579d3c86c2c0173d8469c5e1e81aa55e8621c6d26c", + "test/presentation/event_value_objects_test.dart": "8a925b3b14ddcb598c4e97caa09a201c5d612024db97d615e4a0ac150b24a7da", + "test/presentation/home/bloc/schedule_timer_bloc_test.dart": "73c2b9a028902e9888e27b05aa63bceb287d7e63698f5ef6cd8e1b8c03511499", + "test/presentation/home/bloc/weekly_schedules_bloc_test.dart": "db9eb2a1edab29f08b56e7550572876c9c7a50656305aede98ea83fe5050ad42", + "test/presentation/home/components/month_calendar_test.dart": "738b39300b3913e865ec68741906e66738cfad4f373b689555c6fb87812c1e07", + "test/presentation/home/components/week_calendar_test.dart": "f4521de4da825b0066e217d0ddf63cf43213ee2e914a47bf6023e5fb250525dd", + "test/presentation/home/screens/home_screen_tmp_test.dart": "ed9b9e448d887666929055bd0eb1ed03bbefae154c5346586bbd701955e63177", + "test/presentation/home/utils/today_tile_navigation_test.dart": "36b0e271e66801188d2e58fe067370ea7187bcbfceb51f83fac48d00af332a80", + "test/presentation/my_page/my_data_export_test.dart": "287ef55228ca372822de727e948f8b86ebdf8f9e2c2b5980edf66130ace4e9be", + "test/presentation/my_page/my_data_restore_test.dart": "015004e6c96f8c6608768cab8532bf1e953c7507cec214149bfc810409fd9895", + "test/presentation/my_page/my_page_screen_test.dart": "4f6c9577f8ba8572e28be470764fa9f47e4345d09f56e13b40e5bbbafbdb058f", + "test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart": "7cedba6f7d4dd760e208c498b80948477253cea266123cd082e2176558c621d3", + "test/presentation/notification_allow/notification_allow_screen_test.dart": "5c49a040772d717e262615633d4a3be1e41cf7fc0badb83d528900ab5029f3c5", + "test/presentation/onboarding/cubit/onboarding_cubit_test.dart": "d12c9c3cddefc66a3215bcb5d294894fc70c0b78a6f1edfde965e54522ef1e56", + "test/presentation/onboarding/screens/onboarding_screen_test.dart": "d35a018b847eb73090eab6b02f049c157f385db2f7b820a48bbe0eeae516fcbc", + "test/presentation/recurring/recurring_form_integration_test.dart": "889e5c4cd7f1d954f3eb2eb2d18c7b226f93a8ac6e40ae00962393a90b4506cd", + "test/presentation/recurring/recurring_screens_test.dart": "bf7ff7a7f88ef46fb5512e6ed9d7316636ef3d6f17ba9b7146abb52d6b2311b9", + "test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart": "36f843ff1874b6d0d547da124178c3cf54be8ff5a15df1242bbfe7e064c6519b", + "test/presentation/schedule_create/bloc/schedule_form_state_event_test.dart": "4bea86ea6ae7f791752d283dd2082ae0324c3e5ecdc8f0c94c977be4c3d70fa8", + "test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart": "b911cd03cb272ab31e151e9358a45622ec376125e8491c9784f8877f76a30108", + "test/presentation/schedule_create/components/schedule_multi_page_form_test.dart": "2483c512f484a13c9b2619abefea70d135cc32df5f54ce349ec7ddaf293508e2", + "test/presentation/schedule_create/input_models/backend_validation_input_models_test.dart": "78239f8b01b32aaf04eef14de904b32dd435d2d9e0cd40733e20cce7ecf5300b", + "test/presentation/schedule_create/preparation_form/preparation_form_create_list_test.dart": "e172683255e169cbb427ba15b5bd667f46630675804d9459650526d9a581dd1f", + "test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart": "f2a18aadc5d7fb9bc34750f55f4e8c13e7d97b9e31cac62f27174b4e467d62d6", + "test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart": "d2abdd2f9ff7b959be18057ed95e6ec881f2d89eaba1d8a0b6827e47e694f276", + "test/presentation/schedule_create/preparation_form/preparation_form_state_event_test.dart": "d4a3eca6ad1ae543618c31ec08347aea66f2a53ea0212c1299bd20687f56b167", + "test/presentation/schedule_create/preparation_form/preparation_step_form_cubit_test.dart": "fbe84161f025163745d3cc4a36d73c5eab395629ab21ed60d149ac231bd847c2", + "test/presentation/schedule_create/schedule_date_time/input_models/schedule_date_input_model_test.dart": "25030686e6f9a93adfafd7c3e8436108ad34600ba8049e982883c1aafa86b8bd", + "test/presentation/schedule_create/schedule_date_time/input_models/schedule_time_input_model_test.dart": "730d0f24f96a04ce329402da4ba27107d028232966717fad59f0b8cf7c75c7b6", + "test/presentation/schedule_create/schedule_date_time/schedule_date_time_cubit_test.dart": "030ab2b0e10c33933023155842734b8ddde7db6c07e73dd82a46f9e3bc16b5b0", + "test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart": "5635da2133583f2cf45942eaf36c2e44f286a56c6d7d7622a64568e0ad7fe29a", + "test/presentation/schedule_create/schedule_date_time/schedule_date_time_state_test.dart": "917f3e6c2d7a85ad59172a9ce0a4647ad55a3b2d204b82d68a8435dc299109e4", + "test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart": "26f5dea878c44875d3cf6392ffd44fe3928d4f75266e442b22aa8363485992d2", + "test/presentation/schedule_create/schedule_spare_and_preparing_time/cubit/schedule_form_spare_time_state_test.dart": "867e648c05854e27e5abdd2fcb314545f9ea5b4e46cc2dff2d47d789b343f1e1", + "test/presentation/schedule_create/schedule_spare_and_preparing_time/preparation_form/bloc/preparation_form_bloc_test.dart": "f2280df0f9bad6aeb0a0a6f27e00917c9a2a50e2c6328f77a97c4f1dfc96e121", + "test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart": "03cb5f04f6d29ada101beeb53fadb3341c2effa40b9a128ae956e0e143334df9", + "test/presentation/schedule_create/schedule_time_inputs_cubit_test.dart": "d63de9fe41f195450cbc080864d7301fe43a9a399bbb93741098048a5e1e1303", + "test/presentation/shared/arc_indicator_test.dart": "5188c06cc45a9a8eb100567b3c9e2cef3f8e615aeedcc1279dfdd283fedb7ac9", + "test/presentation/shared/components/calendar/schedule_marker_builder_test.dart": "70ec3d801ba70ace6d9ccd3d7cb0326e694096b64db0cbbcb01d6c9f8c4da9f6", + "test/presentation/shared/components/cupertino_picker_modal_test.dart": "8b813a5aca41660f2aef4eab2ffbda0cce9af44883eade6100e2dd7f6e731a47", + "test/presentation/shared/components/custom_alert_dialog_test.dart": "77a4dc5c5996fd715c1c892007bbf1c40ebb4543735c072bd81a32582cf4802b", + "test/presentation/shared/components/modal_wide_button_test.dart": "49e7355c7ee17c7c6ea9558a4f7dbde2b9f7614a0cfb2c56009badb142c64214", + "test/presentation/shared/components/two_button_delete_dialog_test.dart": "b071ed380e919f68a044da615daddd8ed77ece897dd9c6e934f7e1e26f44f206", + "test/presentation/shared/constants/app_colors_test.dart": "d704a295c34a816744d08577a7ffb159c3528cb9c8371400ca40af32ad27171e", + "test/presentation/shared/constants/early_late_text_images_test.dart": "0b41247d2ea793b50a9d2e9accbc27dc451412b5f5b7f587303b7d3d6b9a7b2e", + "test/presentation/shared/router/app_route_transition_test.dart": "81ff608cbaf8d1090c8428c83d1086d57da27475b078cbbc00fc9b37efe9aaf8", + "test/presentation/shared/router/go_router_redirect_test.dart": "cc40181de0cae9e94c13b451377d055f965af2a2a800ed3a22bc2776e54bfe97", + "test/presentation/shared/router/notification_launch_widget_test.dart": "fdd9fdbe55d1cc9b848ac00ac49c61eda25b6f595d453e99ef2da286d9cf27da", + "test/presentation/shared/router/route_arguments_test.dart": "1050f98213384ef4d69346546f3782a12f176551856aac8aa5524d344e3bdfc8", + "test/presentation/shared/theme/button_and_calendar_theme_test.dart": "545e448abde56eab8adc8473a46a1c43a22782a44f576a04c0aa40cf22b45b87", + "test/presentation/shared/theme/tile_style_test.dart": "b7d75fbd06d31f8b399dd54af958a60f8af22fc71afa216dc4952c2d68aeaa00", + "test/presentation/shared/utils/time_format_test.dart": "b92dbbb3cead69fb424becf32ffe3bec0d6da2ee27152aea8e89082af039f31e", + "test/presentation/startup/local_reset_recovery_test.dart": "f1afb3d065a4b4fbe8ee14aa66205cd8b74eea589da8fa011d00d3bbf6ec91f8", + "test/repository_structure_test.dart": "b9272c637e764758f71192bfcfa8a1a90d9db8928f1c76d928dada56ee51686b", + "test/tool/test_android_notification_manifest.py": "6d51cf68d64339b9159ffa35c693c5edd65dc37e5827235b5913a168508454e7", + "tool/check_android_notification_manifest.py": "3cdce3147005cd9d789bea4b7df49623f2cfe84d8f0dee2cafdf5f3a88570479", + "tool/check_coverage.dart": "4e124968dd49d06f20c17db2a0f70b13712a65ecc47c1eae24618b1d929356f5", + "tool/check_generated_dart_policy.dart": "81e732eba020741605fd7352af7cacb876c033d20478ad0e1cb7f48af36f1662", + "tool/check_local_only_boundary.dart": "d40d2362d02309d5ddc57a9b426a1d56eb0e90735b13b7039661e6ff92cdc05c", + "tool/qa/capture_reset_recovery.dart": "97cd0c27cc9cadcfb1d4f21dd5c894ec88e3eb84f3fd57ac0c151551594358dd", + "tool/qa/reset_journal_crash_probe.dart": "7a76a03f2dc08c17a8834ee0f8a1d9abb139ecc048e92d80970f3b17684f27e9", + "tool/qa/verify_reset_crash_recovery.py": "daa52732605ec0b75c165bf453d2085ca0f7eb03380e37162f40340ebab4de69" +} diff --git a/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-p2-targeted-final.log b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-p2-targeted-final.log new file mode 100644 index 00000000..0be03cf5 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-p2-targeted-final.log @@ -0,0 +1,73 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: independent file retains only cancellation identity across reopen +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at readBack reopens a complete committed generation +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: scrubbing irrecoverable identities retains a content-free uncertainty marker +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: scrubbing irrecoverable identities retains a content-free uncertainty marker +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: scrubbing irrecoverable identities retains a content-free uncertainty marker +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: scrubbing irrecoverable identities retains a content-free uncertainty marker +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: scrubbing irrecoverable identities retains a content-free uncertainty marker +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: scrubbing irrecoverable identities retains a content-free uncertainty marker +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: only a fully completed reset journal can be removed +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: only a fully completed reset journal can be removed +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: only a fully completed reset journal can be removed +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: only a fully completed reset journal can be removed +00:00 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: only a fully completed reset journal can be removed +00:00 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: malformed new content metadata never drops registry ownership records +00:00 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: malformed new content metadata never drops registry ownership records +00:00 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: malformed new content metadata never drops registry ownership records +00:00 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeError remains partial without guessing ownership +00:00 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:00 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: explicit reset after damage cleans quarantine only after complete receipt +00:00 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:00 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unknown ownership clears only after an app-wide provider receipt +00:00 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unknown ownership clears only after an app-wide provider receipt +00:00 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unknown ownership clears only after an app-wide provider receipt +00:00 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at renamed recovers through a new owner +00:00 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at renamed recovers through a new owner +00:00 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: returned cancel failure permits content deletion and survives fresh-instance retry +00:00 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: returned cancel failure permits content deletion and survives fresh-instance retry +00:00 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: returned cancel failure permits content deletion and survives fresh-instance retry +00:00 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: returned cancel failure permits content deletion and survives fresh-instance retry +00:00 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: first pending quarantine crash resumes without deleting user data +00:00 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.database deletion stage +00:00 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.database deletion stage +00:00 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.database deletion stage +00:00 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: false +00:00 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.preferences deletion stage +00:00 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.preferences deletion stage +00:00 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: journal-only uncertainty from temporary iOS observation failure converges on fresh startup +00:00 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future staged version is not overwritten beside a valid committed journal +00:00 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.launch deletion stage +00:00 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: failed marker removal reopens completed journal without deleting data again +00:00 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: completed-journal unlink failure cannot replay deletion against new content +00:00 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unverified intent write reports no completion and never deletes data +00:00 +71: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/final/root-final-analyze.log b/plans/audit-2026-09-23/artifacts/d03/final/root-final-analyze.log new file mode 100644 index 00000000..c501fe14 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/final/root-final-analyze.log @@ -0,0 +1,2 @@ +Analyzing OnTime-front... +No issues found! (ran in 2.4s) diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-cut.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-recover.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-cut.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-recover.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-cut.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-recover.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-cut.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-recover.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-cut.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-recover.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-cut.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-cut.log new file mode 100644 index 00000000..ad908482 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-cut.log @@ -0,0 +1,2 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-recover.log b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-recover.log new file mode 100644 index 00000000..ad3ba24d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-recover.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/reset_journal_crash_probe.dart +00:00 +0: isolated reset survives a killed host process +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/host-process-kill/result.json b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/result.json new file mode 100644 index 00000000..83ebd510 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/host-process-kill/result.json @@ -0,0 +1,37 @@ +{ + "scope": "host process death; fixture providers and deletion adapters", + "mobileOsVerified": false, + "powerLossVerified": false, + "results": [ + { + "cut": "first-ownership-written", + "signal": "SIGKILL", + "freshProcessPassed": true + }, + { + "cut": "intent-renamed", + "signal": "SIGKILL", + "freshProcessPassed": true + }, + { + "cut": "database-deleted", + "signal": "SIGKILL", + "freshProcessPassed": true + }, + { + "cut": "completion-renamed", + "signal": "SIGKILL", + "freshProcessPassed": true + }, + { + "cut": "marker-removed", + "signal": "SIGKILL", + "freshProcessPassed": true + }, + { + "cut": "journal-unlinked", + "signal": "SIGKILL", + "freshProcessPassed": true + } + ] +} diff --git a/plans/audit-2026-09-23/artifacts/d03/intermediate-boundary-45.log b/plans/audit-2026-09-23/artifacts/d03/intermediate-boundary-45.log new file mode 100644 index 00000000..5b1211f5 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/intermediate-boundary-45.log @@ -0,0 +1,47 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_actions_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_actions_test.dart: marker absence is read back after deletion +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: pending reset preserves its ownership and bypasses unrelated privacy cleanup +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unknown ownership clears only after an app-wide provider receipt +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unknown ownership clears only after an app-wide provider receipt +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unknown ownership clears only after an app-wide provider receipt +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unknown ownership clears only after an app-wide provider receipt +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: ordinary ownership does not authorize reset or remove user data +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: returned cancel failure permits content deletion and survives fresh-instance retry +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.preferences deletion stage +00:00 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.preferences deletion stage +00:00 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.preferences deletion stage +00:00 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.preferences deletion stage +00:00 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.preferences deletion stage +00:00 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=true +00:00 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:00 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:00 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.credentials deletion stage +00:00 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.launch deletion stage +00:00 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: failed marker removal reopens completed journal without deleting data again +00:00 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: completed-journal unlink failure cannot replay deletion against new content +00:00 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unverified intent write reports no completion and never deletes data +00:00 +45: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/intermediate-full-829.log b/plans/audit-2026-09-23/artifacts/d03/intermediate-full-829.log new file mode 100644 index 00000000..759f2680 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/intermediate-full-829.log @@ -0,0 +1,834 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:01 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:02 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:02 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:02 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:02 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:02 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:02 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:02 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:02 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:02 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:02 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:02 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:02 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:02 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:03 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:03 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:03 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:03 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:03 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:03 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:03 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:03 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:03 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:03 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:04 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: independent file retains only cancellation identity across reopen +00:04 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:04 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:04 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:04 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:04 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:04 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:04 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:04 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:04 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:04 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:04 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:04 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:04 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: initializeLaunchHandling dispatches cold-start alarm payload +00:04 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: initializeLaunchHandling dispatches cold-start alarm payload +00:04 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:04 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:04 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:04 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:05 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: localizedNotificationText selects Korean text only for Korean locale +00:05 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:05 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:05 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:05 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:05 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:05 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:05 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:05 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:05 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:05 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:05 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:05 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:05 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:05 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:05 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:06 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: explicit exact denial retries once and returns actual approximate receipt +00:06 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unrelated provider error is never converted to approximate success +00:06 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: failed approximate retry stays failed and is not retried again +00:06 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unknown timing permission uses approximate rather than claiming exact +00:06 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: display denial blocks scheduling even with exact access +00:06 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: settings response is not proof of a grant: permission is reread +00:06 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: iOS timing access is unsupported without calling Android plugin +00:06 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real storage retains every failed platform ownership for the same Schedule +00:06 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:06 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=true +00:06 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:06 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:06 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:06 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:06 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:06 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:06 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/early_start_session_repository_impl_test.dart: getSession returns null and clear removes saved session +00:06 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:06 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:06 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:06 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:06 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: stale first occurrence approval asks for review without changing durable data +00:06 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:06 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:06 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:06 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:06 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:06 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:06 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:06 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:06 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:06 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:06 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:06 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:07 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:07 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:07 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:07 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:07 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:07 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:07 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:07 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:07 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:07 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:07 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:07 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: multiple preparation steps are a no-op even when caller omits links +00:07 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: missing profile rejects updates and never reports a successful mutation +00:07 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: empty profile starts without a durable revision and creation is race safe +00:07 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: raw revision update notifies DB watchers and failed profile write publishes no success +00:07 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:07 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:07 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:07 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:07 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:07 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:07 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:07 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:07 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:07 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:07 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: saves and loads the early start timestamp per schedule +00:07 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: returns null for missing, corrupt, and incomplete session payloads +00:07 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: clearSession removes only the requested schedule session +00:07 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers map schedules to and from database rows preserving stored fields +00:07 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps users to and from database rows preserving profile values +00:07 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers empty users cannot be converted to database rows +00:07 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps places and preparation steps to database rows +00:08 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:08 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:08 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:08 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:08 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return an empty preparation list when user has no steps +00:08 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:08 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps when rows are out of order +00:08 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should keep remaining preparation steps when a link is broken +00:08 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return each preparation step once when links form a cycle +00:08 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should return one preparation step by id +00:08 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:08 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:08 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: updatePreparationUser should update a preparation step +00:08 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:08 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:08 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:08 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:08 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:08 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:08 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations createPreparationSchedule stores steps as an ordered linked list +00:08 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:08 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:08 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId orders three linked steps when rows are out of order +00:08 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: deleteSchedule should delete a schedule from the database +00:08 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId keeps remaining steps when a link is broken +00:08 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getScheduleById should return a schedule from the database of given [id] +00:08 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getScheduleById should return a schedule from the database of given [id] +00:08 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById returns the stored step contract +00:08 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:08 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:08 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] +00:08 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:08 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null +00:08 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:08 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:09 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:09 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:09 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:09 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:09 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:09 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:09 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:09 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:09 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:09 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:09 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:09 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:09 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:09 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:09 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:09 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:09 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:09 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:09 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:09 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:09 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:09 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:09 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:09 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:09 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:09 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:09 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:09 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:09 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:09 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:09 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:09 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:09 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:09 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:09 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:09 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:09 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:09 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:09 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:09 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:09 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:09 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:09 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:09 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:09 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:09 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:09 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:09 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure does not undo committed start or skip its snapshot +00:09 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure after finish still clears completed runtime +00:09 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:09 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:09 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:09 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:09 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:09 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:09 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:09 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:09 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:10 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:10 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:10 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:11 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:11 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: a previous civil date in a negative offset still supplies a future alarm +00:11 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: a previous civil date in a negative offset still supplies a future alarm +00:11 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: a previous civil date in a negative offset still supplies a future alarm +00:11 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:11 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: off retains a cleanup warning and denial never becomes precise +00:11 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: no future registrations never becomes precise from capability +00:11 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: iOS alarm and notification language remains capability appropriate +00:11 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:11 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: empty user exposes null convenience values +00:11 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:11 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:11 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:11 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:11 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:11 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:11 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities calculates left time before leave with explicit now +00:11 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:11 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities schedule with preparation derives start time and total duration +00:11 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:11 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:11 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleEntity diagnostics string representation includes schedule identity for diagnostics +00:11 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleState timing helper durationUntilPreparationStartAt uses injected now deterministically +00:12 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:12 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:12 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: minimal progress hydrates names/durations from current source only +00:12 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy exact fingerprint additionally requires all step fields +00:12 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy launch becomes a minimal versioned route, never a start command +00:12 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:12 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:12 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:12 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:12 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:13 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (setUpAll) +00:13 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:13 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:14 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:14 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:14 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:14 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: compact height clamps calendar rows without overflow +00:14 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:14 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits without scroll at 1.3 text scale +00:14 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:14 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:15 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:15 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: swipe action button height increases when schedule is expanded +00:15 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:15 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:15 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is available before preparation starts +00:15 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is available before preparation starts +00:15 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is available before preparation starts +00:15 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is available before preparation starts +00:15 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is available before preparation starts +00:15 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is available before preparation starts +00:15 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is available before preparation starts +00:15 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:16 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:16 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:16 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:16 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:16 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:16 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:16 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:16 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:16 +549: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:16 +550: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow entering ongoing applies catch-up tick and accepts later ticks +00:16 +551: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +552: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +553: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +554: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +555: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +556: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +557: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +558: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +559: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +560: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +561: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:16 +562: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:16 +563: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:16 +564: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:16 +565: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:16 +566: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:16 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:16 +568: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:16 +569: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:16 +570: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:16 +571: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:16 +572: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +573: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +574: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +576: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +577: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +578: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +579: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +580: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +581: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +582: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +583: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +584: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +585: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +586: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:17 +588: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +589: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +590: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +591: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +592: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +593: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +594: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +597: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +598: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +599: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +600: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +601: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:18 +602: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:18 +603: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:18 +604: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:18 +605: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:18 +606: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:18 +607: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:18 +608: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:18 +609: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:18 +610: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:18 +611: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:18 +612: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:19 +613: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/arc_indicator_test.dart: ArcIndicator paints background and progress arcs without throwing +00:19 +614: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:19 +615: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:19 +616: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: date picker saves the initial date value +00:20 +617: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/tile_style_test.dart: copyWith keeps existing tile style values when not overridden +00:20 +618: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/tile_style_test.dart: lerp interpolates all tile style dimensions +00:20 +619: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:20 +620: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:20 +621: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:20 +622: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:20 +623: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:20 +624: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:20 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (tearDownAll) +00:20 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: (setUpAll) +00:20 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:20 +626: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +627: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +628: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +629: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +630: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +632: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +633: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +634: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +635: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +636: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +637: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +638: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:21 +639: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +640: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +641: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +642: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +643: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +644: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +645: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +646: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +647: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +648: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:22 +649: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:22 +650: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:22 +651: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: shows only local data and device settings +00:23 +652: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +653: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +654: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +655: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +656: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +657: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +658: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +659: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +660: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +661: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +662: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +663: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +664: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +665: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +666: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:23 +667: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:23 +668: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:23 +669: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +670: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +671: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +672: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +673: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +674: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +675: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +676: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +677: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +678: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +679: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +680: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +681: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +682: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +683: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +684: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +685: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +686: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +687: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +688: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:24 +689: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:25 +690: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:25 +691: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:25 +692: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:25 +693: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:25 +694: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:25 +695: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:25 +696: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:25 +697: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:25 +698: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:25 +699: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:25 +700: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +701: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +702: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:26 +703: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:26 +704: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:26 +705: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:26 +706: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:26 +707: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:26 +708: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +709: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +710: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +711: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +712: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +713: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +714: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +715: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +716: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:26 +717: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets active alarm close button navigates home without confirmation +00:26 +718: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets active alarm close button navigates home without confirmation +00:26 +719: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:26 +720: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:26 +721: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:26 +722: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets manual finish after leave threshold sends positive lateness +00:26 +723: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets manual finish after leave threshold sends positive lateness +00:26 +724: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: dismiss keeps notification delivery when fallback is available +00:26 +725: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue shows live leave countdown for ongoing flow +00:26 +726: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:26 +727: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:26 +728: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +729: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +730: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +731: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +732: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:27 +733: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:27 +734: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:27 +735: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:27 +736: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:28 +737: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:28 +738: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:28 +739: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:28 +740: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:28 +741: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:28 +742: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:28 +743: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:29 +744: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +745: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +746: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +747: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +748: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +749: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +750: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +751: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +752: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +753: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +754: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +755: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +756: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +757: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +758: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +759: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +760: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +761: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +762: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +763: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +764: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:29 +765: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +766: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +767: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +768: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +769: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +770: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: settings and review render at phone width and large text +00:29 +771: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:29 +772: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:29 +773: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:29 +774: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_time_inputs_cubit_test.dart: place and moving time cubit validates fields and submits to form bloc +00:29 +775: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_time_inputs_cubit_test.dart: spare time cubit detects overlap and submits spare time +00:30 +776: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_time_inputs_cubit_test.dart: preparation changes update total time and notify the form bloc +00:30 +777: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: fills the bottom safe area while keyboard is hidden +00:30 +778: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: paints a white backplate behind the iOS keyboard inset +00:30 +779: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: does not add keyboard backplate while keyboard is hidden +00:30 +780: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:30 +781: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:30 +782: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +783: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +784: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +785: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +786: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +787: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +788: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +789: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +790: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +791: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +792: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +793: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +794: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +795: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +796: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +797: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:31 +798: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: shows explicit choices for a repeated DST time +00:31 +799: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:31 +800: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:31 +801: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +802: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +803: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +804: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +805: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +806: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +807: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +808: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +809: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:32 +810: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:33 +811: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:33 +812: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:33 +813: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:33 +814: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:33 +815: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:33 +816: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +817: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +818: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +819: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +820: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +821: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +822: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +823: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +824: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +825: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:33 +826: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +00:33 +827: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +00:33 +828: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +00:33 +829: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/kotlin-compile.log b/plans/audit-2026-09-23/artifacts/d03/kotlin-compile.log new file mode 100644 index 00000000..ae38fa60 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/kotlin-compile.log @@ -0,0 +1,33 @@ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:117:20: warning: 'fun setDecorFitsSystemWindows(p0: Boolean): Unit' is deprecated. Deprecated in Java. + window.setDecorFitsSystemWindows(false) + ^^^^^^^^^^^^^^^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:136:44: warning: 'static field FLAG_DISMISS_KEYGUARD: Int' is deprecated. Deprecated in Java. + WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD, + ^^^^^^^^^^^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:143:67: warning: 'fun get(p0: String!): Any?' is deprecated. Deprecated in Java. + rawExtras?.keySet()?.associateWith { key -> rawExtras.get(key) }, + ^^^ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:337:23: warning: 'fun vibrate(p0: VibrationEffect!, p1: AudioAttributes!): Unit' is deprecated. Deprecated in Java. + vibrator?.vibrate( + ^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt:463:58: warning: 'field scaledDensity: Float' is deprecated. Deprecated in Java. + val scaledDensity = resources.displayMetrics.scaledDensity + ^^^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt:267:59: warning: 'fun get(p0: String!): Any?' is deprecated. Deprecated in Java. + extras.keySet().associateWith { key -> extras.get(key) }, + ^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmReceiver.kt:271:14: warning: 'fun setPriority(p0: Int): Notification.Builder!' is deprecated. Deprecated in Java. + .setPriority(Notification.PRIORITY_MAX) + ^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmReceiver.kt:271:39: warning: 'static field PRIORITY_MAX: Int' is deprecated. Deprecated in Java. + .setPriority(Notification.PRIORITY_MAX) + ^^^^^^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmReceiver.kt:277:37: warning: 'constructor(p0: Int, p1: CharSequence!, p2: PendingIntent?): Notification.Action.Builder' is deprecated. Deprecated in Java. + Notification.Action.Builder( + ^^^^^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmReceiver.kt:321:59: warning: 'fun get(p0: String!): Any?' is deprecated. Deprecated in Java. + extras.keySet().associateWith { key -> extras.get(key) }, + ^^^ +android/app/src/main/kotlin/club/devkor/ontime/NativeLog.kt:37:59: warning: 'fun get(p0: String!): Any?' is deprecated. Deprecated in Java. + extras.keySet().associateWith { key -> extras.get(key) } + ^^^ diff --git a/plans/audit-2026-09-23/artifacts/d03/reset-complete-en.png b/plans/audit-2026-09-23/artifacts/d03/reset-complete-en.png new file mode 100644 index 0000000000000000000000000000000000000000..4af2afd5838c42a6d1f73cea3981d96b91d16a9f GIT binary patch literal 87555 zcmeFZWmJ}1+ctU|sLL)y+D1h{K)Os2QA9zJR$98dOcV(V5GfUlWjSG@Sz@OxdtWuLnTu3x^c zz{Pbu;!H*MNY6}x^*Cc_S7KIq&k3&r%P-1-<~bt^mX4yXWfP4vUqGl0{OZtwH2i+&4%`Ff=>~QR#pV238ocAc{Y|*m9sj=k z-wlve=EViHS*tf@Nc917Zd!Sf|g$N=P^+zyxLtw-tiWFmABF@?nbL*@bWeo zB!(*_YX6RqWzqW`er<4!aytZw??S3(;mbGtrs@v!ePNuI{lDJ`TIxAWR!DG~cb9m? zvK~A3bSSk&)T-`|Yn{`=kVXNcwfoX^1IKieb<6DXOb)ZZVfWds6t^hFyZDKBrM%Nq z$ab*JoN2b_)Gm3mmq$;|ot`hYwcsc|>A^Npe#Io+9Sb8l&Vz~NqSvBs#@uIKPvI3e zStWC%CRT;Mqrl6nC&->X$bR_CtprU;Kc<|I9}oRWF{BR*veO&<(i5a*XF!Q$|3-EC zx|Gj}r(#QeSI5G{mhQgt-QeS`UHbMyhSg9z`GrAp_02q{^)8n#o2+`C8+jwDy^tl? zy*bO?cdkC#Aj!Bzls$X4JzOovDPCgLyPZ3gDZ79FN!IU(b6(P)?5kqI#a|mV<*NB4 z<*VD0?f5%dA+aumB15q9M*NMlBEx#bW@TySnaCw+I$ndlIV(#e9X}6xotQpKk=uEH zlS}56AYlQ!nPJ9*;_gSMk5X0O8q%i!=xfY0S#yvVnUEK4W)>A@Pw)Mp?)Bn)u&87E zZ`rGC(|aj$xADh-*FHY^A~TJQ2&~(%mjJlq7rpe92oLR@}fdt~ti| zs;gomu{q20b?VR9UNUcTDU!fFCu>mpm+zjuHMJWtyAW{i6ir*=;G*)`-3C19bR4y~{ldXD9osS@S*UeQTYV949OIN58Jw#7jf*_HA8#`_+*v zZ+DBW%$1v~wuD~`=|vncSpL37704%z#W<8_auP>Bv2c!=@ zk4{;DZd1BtNq_+NK(ggPjYnCaP{FaVfSvxv*|sm|%#sn5Hps1x8YSb>xt)b1x}9c! zFz<2Tr5xY(jq2;V7{%n{Wugm%dxfkFYV?(o^{AKTr=AH~_8;<#k=b+N)^>Si(+lnl z0$l(3d|y?&WBoNT{+f7ycBz{Ph0;~Kg|G3stU;i4vn{7d>sK>or5!tv%1`gDS)Xs) z6X-kjN5H)Me1RvU##Dc`r1qM3?YopI(X~Iei7_}YPXs5b)+Xwdlh`b7I{c76ajx6H zAh}kRUc`R7#BS&fr^bh?!J?AgtKYSoOYWzvXifH3n3f6jXeNguH`i^U+s&q!bdgtH zf!u(y$v3Kal$7Y2CVaDP)2+2GMUp0~M%hqKo(3;=vM5kVARIcbI?v0VLO}N!XldoU z)0!g03fyVAxm0DZU*CC9!t+Gk5C2u+49eFxd`)dMFBehBt4Zfp@;;o>-m*$S_t!g= zlR}%)mxrZ37`J3g_Tbt~A8&FocEIPDcD|6%PVT<9p6SWW7{$Z>5pTpj8T_V7PX#%Q z7uGqiEWIjSdpfUuGYzGsw>D1gW`p%WO-yC}&BcY9Y3yXN-j&)|m8RtFF2>090|(UG zbDxeaPL%6pF5L|a+|v{6>{RJj^kAY~OiUW7=$;xZY-=WMZN2uLnHiR6#}3mP1)V%4 z<1Furlu~~_uWt|5OCStimEYKdP@onf(XiPJu9f~{iVBHwfP6FGr ziT^N#`UST#B^R0V!qgxyheIU#L_E*!uP*$Z&(F2{=>*lc@+Rw57xkMJ`*G{!BN>$6 z{j)0k=UST6)T#K2-lh!e{023%!o%?sWkM{05z&pbSgq4ndZv)Bkjln zs_Z{BFDU!cD3NJ94vOb~lzheYCk2}-Xwm!JAvEDvoLWwAOU_EFXi7wH|F8Gw@>AV{ zPu;!lPRDb6L||2T-QTZ_|2i8h9VmDqP|)&and~0ipXc6_x4+tnrN;M0%7#f>^#4BA z(IJe46DZ$sx~nymmU5$Rac&~ScA}fsu;JaA2R2jH@*&u(!k$ZgDGf%?GDfoPUo|w@ z4aAsMO0q1~7RX26+9APYt4Xpfq{D-rk3FTysv$s|3L*J;)4Pqu-T7NpGOS`on#_vX zOh>Y2w39>4d&)RX+jCduXU7Vdnemz;U1sN{0kuTIBma5#hdpeHugAZ}LUaR;9Gqh?ahr8VA{~<29pR>x#rvwx?MXNmDLLB0oH^R3YMUSRHvI>yth>B8M{AfG|7NzlPBAPxL?2ge+GJUq7vd%QcIwaB+?$#^lsD0^mkjVz z8vQv8eB-VD_L=QHt? za2vA1Nmv#4l!iO5jLQQW^v_}}uS?Xv<Pe%cwn*I?z{{%PPKR_w?(Dj6y*JIOZdAD!*bILc;eRbT8&uZdRJg*5VD z33foUIi}89GeKu~F*Jv7{pl{~g^}+rAH0K7)-pdK&=c@#Dt;riihfP>Q+Ac~TtJY( zsXub@)C0C2ydMD@sKj_FUBC(9_erX{r}zt552r3WAfNLP+b^fa#y!a8hH* zbD^DKQJvdVra1j|Ah<&X>)$z`N8OMK{l)`KvNB&sS}G!;Z2rv`@xM3S9~I}R6J&d2 zKqJ&MrtYhLk|<0|FM!=w6;^7_OH+1YPJix(T__I{Nfb3o`Mb!yjbF@#QF;}a zqfVeujMF*FOG-*s3Q&#)|6RO3+E|ag^9?D69o_!MW%G4@f4}Yn3fbQ-;OL&<7W4B! z8kdJMLNDF_H-ETd(KeZZDLCRYZti<{|rPYfjIVNKeTBa~8&^7)RhdS^!N?sxXW zuz!VBeUNE->-|k1qx^KWX7{b2y1Ah+Z|_DilR^a}SMqrtj(PN1pi>8AJR zT+`K>Vsmkp7&S^phAh=3XO`zHH+a! zfdNauqgDnNeBvaZ?O}Uaw)*tkVT5hCiggoDM@xs@T$iu8VE$oW<(=qNMR$iDzjnm} ztG-IN9LSH&7=G)(L@8pymOe^0Y7z_?dc2L93EN)Uw>M{bDz3wgHTLJ-A!~2ny0_p+ z1h|J7UKO3|?3|#;IFULq*c;+iX48=ExX|qo;K?YIZ|pSTA5beHfW;Y4{&`AoRd@gi zu@qoHaTF`=KUQ_0?{GRT&IWi{;@S$dd{um^{*3Qd0u3skou0zddt#jr1^?qRPFjE| z3QMs~14xVns%u$6`gjp*N2L@;sooV^w-{2S|F-^MR6fZb#2CpSWkfP}vlVd0SP^Sn zr)6f#@^q@{SRRc|k|EyoVJL1Kc+WX?UGlrouM|H+PnVg08U`MFIS)h$?XnvB!*l7yW0fXSxyo*AuDu*0|{gLR`Rt#QleJ3sb{I@pk2(x*(3+l=owCQaO3 z$>_`Bjv;!mtaGkgJ&<_paUDKjVQMS$wfx{Y#WxJl#WPSK*(OZsm@QBStmr=nFUX(SmkiH_Hd+l)J9{x0goMxjC;$zpY!{D)tXv z{OQ88J*J=*I?3G?63l;z$9PxqtI``ceb|)t1PWPuN-#T~in8npFqdY)8WaM;s}qd6 zqE-0vFu^C$>}>ANYS*oS9r8VehTj;Zye1eGT+K9dWqCm%MrgM6K?lHrMaX8#GT_tg zEWB$^j8dxSVJRQY-trl% zj)JHO6BZ#(nm7`D&sE{E^U?!SmoBvy#%if9>uG0sR8Ews+PDDi|^t}DoxosY$ysZ9ehgIzowFFIDP{a;)lZmr`!oiaHXsVO^<>47ngysZ!9U*{5`sXu2Iz9X zB(1E-n^l8UQis`5o3=+ZX6gI$t(z>Wu9$ZF>Fm)Ir9`fSc5)M_Q=|3kJtAVJB`u-N4fJXZaDi%Vev+IzZ{QLxjY z>Z!-wU+>NkHiyC!MiM#1X=(mij?)qmeP(AK_~l@+X3?&WY$ASV~qwtHHe;`5vs(Mf)X6-i!yF`T(UL0+s-H2i2Ag3Y-c?!D|l> z@pL=r$$!98biUU(sEdlC1@S~ijzCtZZ5;Vt%*00Gp>dulN8+eAXWF(beo0C?g|AYNib-(;QYYK@ z-`+^Kh3=Gz-#$v0+g}ekmuJtv#_|6POZ`j1p>NRA|IPyZAK}K!aZFR>RG1w`&txKp z72+BUQ=2|q@#mG)UR~4-!O3sxE#N(yVyaP~W>BQT_lN&*nsxq+t_?K#{!cvW1&iN5 z?>_8O-dzgR-H}gk44MoXf{I3NC&9<`Mrr#8ECCWcS}(Om#bp=utrVjlUgFwzTC4Tt zS;{_+>c|_c(;FyVJ_x(<>xznsgF7fiZ2!0Mgxo9K6iU%O08abUb?J@s4LA zOtvV)3emUX4-g)%UmNQYlk(?y${)QbNR#4zWdtyAiKrey%UUiK!=Oit!;pO5%6cF9 z=7W(Nzfn;)qJ8fGUdd5MndP({?<5k1_&{u0yBq40h&t+}2X;=t3KX?7(dQA!a~@PJ zCc?oTK(tNEMP>|48UO_HmKR2tGX@i+2BGPYdaeuS;E_F>54#E-v-6^1UwJB1Ak$V1R6of?BLb+B?p6^lJ_d~Q_ls~hUTr^Ijm->+q6rD! z3k!Pzcv=I{LW;L1L94hOo<(z~)h-t4lDjJ-nG@0_fkJ#yJVc&lULJK7VC0~u?h?8G zdM%CI>1Q+?o={{8urzzT#FlNO7oZBZfU17~hb31*Cy*NIX1IxSL}`$SP%|tL8{9Pa zyp%)WKf8|PqS!AFbgXAEIUV3M-&X+|IQwmz({|_$?4!AJ{-Ta{M5rLzUIB#nzzHX# z?742^r8z)xcMVU0BX4;P=?))0O!elqLkVzYv%^H`YzOnoUIcKFO%tTLUAX$PR8hoL z29cSGuAS22w6M0y#{+8janO%fHdwR}2`?sr5uL9tt3b|4S21iz@qkYg*J_#$v4y|y z?;_ccWLcl_di4yi%tMLT!;!OS_HwBO zSOEW6pFf{FRjbBaz+is))ZNNMh4FQtiFC|Nbq$3xySe@DjMbOkSNR;OunwtaFnVH9=!{5lJc{w?Q*Jv>?7rq`SW^LkKo!47D&9Sgp7-joN!= z1Fy8n8G#gsx$(4-?1gq@#Lrpl#EpF*rEbIpfjC72{>(Pf5myeqmw^yNl*{xIucd{S z713-MBL$;vKZv}qWmY2j>PQ}uRZuAY^o|UEoCFccg=n+~pxO4aKX1%YZaFHBtjX(S zFPvsFTD%L#9&e%FBQ{rfwH+y6zAykFkaXKTh8x<;UVf9?BqfcqrfTkwi0z8YnpQ+g z9+4JXnsj$s9~C*qbVhqE&1+ebmr@*Ar((Z!zg73zNpKY*c%hg5t|!rA4R%A>b0c{B zg8!XBVp$QK3lMVAE`SJ5TLn6wle>S(#3K7({Wi~~nHI5T$C)N|uj>&f6cP^2PxkEs z2xl!CgObFM+TvtC9@k?)XbQnOySvu24sfIO@Duk482}%y>+#G6E|#=+3~wP-CK=Ro z1wk%R_Zs&=^^kx-;5NQ1H$pZ{d|`Sh1TKP>)8Ljb$QknAh1rsf5c`xZDlSM)2gV)r z66V4R-5lgR{s^iy5lo($ty>VA8g3H8-uO=V#hz{6vy7_x6bS9Z?FH74&)CzC5^{Ll zdG77ZhJ=G@?*eRr=suvL#oLUvt9vD=A~BTR_i}zH@#MSn{UNck+JV|w6E(E^|3b}B zeZXo{@L3v*;73;Zv^u!}>5909Boe~Z^2*nYoIY2ET@6mdRX-E_f&S2r3eVQR+20Jc z{el;hNKvU)jaI4s+WK9}GM+@L0a243DFH(OKKIg10`e6su)1%XSsl3ZaOkz~C=5v^G zRj+kU{2Fu1wO>28Ls>=>v2>iz+O{a~hP(Y+?&;wFABEfNOQM7cW8;oUb`o$;vviqcaRp z!zD7-d1aX@t3!z>lSEk{O%M5?;cuAo6c|F7WZ0%XC!Ua7hFsRs=E}_ebBqu;va9-W zStYyw+?f|!*H=IZAs)EFmoA_G85bbE5-Y-hKEU=f9^zqkT3@Jt zf+9D9l(4BYp3FN;OL=O~qveLEX4zc}lMW0=_a`)*Sbw)5Zj;Ygo-23GCshfcfl8no z20{mD&xxPe$Dlk_htyKqcJ=dUVlWBwyXhHE)HA)4DSBAo|g*_MDq_NReboIL?&XHNCnjs_?vknJ6+_#TG~F za2;H#-0)4va6<=5Yw^O)a@;S8wTV9x=X0@e+mS|AH3MXoi1O21pvH_aEkFE7<`7)B z<3HKnEX<5B6EH}0UfP12=W}rtyI}mA5HFpS#mpW+N+ET?d)pxmz!uwb)8)CIf}bxA zlK&%bIS&b>-8yGMB@?~UHuzB*R)folO}jiO(Aq!*0zRka&*#wLc0rGS@?>Q$c;zXe ze*xCL9d)Y2_A!{ZaCcdt|HORJ@H39=qZbX90CYUjIfRVT+8)=D=0pcu3SiAMYx)nX zAleE+wbIuDo+>+AEtGJVcUm?QMUc!{5VtmQUR?BmI#A{_Sm(m?Gb6z>EA~V(ubm$+ zK`lU$Jw$~0=5imq;V(Vy5WhUZ1iCZ5h;RkxmX>r~)kH$2#vo>NWjcZ2+p2Z*!`)3o zAa}TmE#4_xsKffIqxM%ou)xp(3PwIbG43f#7_u-Il|G+~bqYZzu^l?NyyQXr>JShG zfsr3gUwH>1*Ok~a{~cWDtvE=>XB1F1k5IWl3J3GbtOV(VBevsH2$OEhuzZ+-d2vot zQDk^z{4HTDBwjO}Mz57L^o4bb-2c8^S!V5{G|pTC*IXH`Zhg}hgqhnh*ArZ>&rD_& zyjkV7vyA4(yKGywE}_hMAuqdDQl6w3KyQ8E#GX7EQYD9GHM4OtTmpMEWO* zr?BKl^C}_Z`Sxoc?N58Rfi$$t;kT1mD;8Vv8*o%7(xsQtS)V=K#iE@x3?N=Q5Jof~ zi*LqtGJx7(MyzI@X<@-mcCQ*vsAUWH6l?ws5g=JJ#SBiu=!n#n#9M0l1sficI|usPTZ@+g;)(J8J8O zM*Q~*$G@ET56txx2z@uP7)G?WIH?8VOi#TfVcXL&g?w}xe-+%B>7)Vuc*lPX3v7D_ z1`k?#d1XT{Kh1ka)04D8A?;N4n(%aeySdBYqN!1`AoLY(UL^C^^7L{KZoe z1Aqh@6XFbMqfY#V;;r`?aW+*rbbq=W2=?63_z1swk#A{cGC{O0H<1M%TB>dxxbga$| zpA!n#kBmrOcrj2rgUn(+=>hhv$;wVKp2e$Jd3iEwAOU9uKv#Gsy^|{WZM&e!1)p zHUufDKUIIwfb)PB)kghlbkH9?Nxk|DIA(}Y5S?NSg>|u;FQ;(M=9t!k?jy@R?8>)B z72FCDiybe|9zDmcUGme`kbT>a68Ts@cQPsEfQ0mKWDl+laa@=dh%3#5cH&o+?lw>3 z5bH%}QdqV!2ZzU8)xOcRfZD?63cAA;Kc+ZgsEeg$f)5gIf4hHZVzebwLhe)<7KONF z(gV_n#uVOa*BOsp17UIWzbP(f13if$p_F%uP`*r;&D=FQj4Q0QJ%6YNxT*;Hj}r>NY3Woavux{ibdT@uv$RErl@EM)w=I<|N@E42 zu%WjHGj!(5-LY5=ftUvg2^hZID`=rBtq*Z_NitL*MdeY5cUgtubOPSOKb&9*(Hhi??UdcAa~c9}@d!;N2B=5KI*KaqcgTLz#)Ifm$Y>(IqEkV#7h?x`97ab&G#bka?xtqNo1a7$?;&g;G?l&0E za%UD3g)y%>Xqw%&{>(~&{o0lzY46-M&HN{)jtyjt#gsq(a90*UEcsh`*SwtpsIEXv zR4>U^B5MK2bHnPC8#JwZsLXK;Q!iHEbRsSmv^*~tTM^FpWTXdSEgQJgckD)r5RZ1T z{aSkR@tt;0EA}WET*HlE7*(k&$p|%?eo;AAhXRq^Oi}(?sUJ6sxclB6q zB_VDCY77%gd8vXA(|7gd#B~+>77*|p5f-U(7LE8p*0tYw2p+JWiqr=%XT} zU`1!7xx+qsMa)yoyS^m#~=*@@n21o|2cesgImLOSh5l(&2*b!K=Tb7w~&Kgj#%~>YrLaqZBM#rup!e-Lrg=eafa&1%(H=4u{}NMu`~7o zA3l6&Z1QYcp6%FXo!ZJ3aPTYVTK2CO+*Mt)9tkOn+o#!1guV~f>Qs$l>OM438z+xe z)!6iSzr%w?F+QWllX5-7U%Q4mZ16d!Y{xY(oIjtIyzStyZ@FmC1`~#Rn@o0u+$#=b z-QBEys#9}!qU&$Zg^Q!u(5U73eZpgCtp>whQ*%@ zJsVCw{z{d0QQWIm>(JF8;n11(NT%WBf>+B9hw{9$G%X=nS3Tcinji}B90$nA3vrovHMm$D{!CZo$r;q ze*I+wvppA?{kah$zQsEA&PJyCIp&byiq1~W)Y#p+lhTB{}f0iZW+10&{GsP!jgPb8G3$&kA z6_`~IGGEK@g?oEaVVvo~&VJ`e2+bYZrMDj0tS_rp*P_*G>B`>KG_0SRFq~}SKIpVO zr!zHBTdp;sz{VmPn=i1sJ1q%XHwWCd(7I~9vH&;G(zoQ*n>XRlpKtdq@%Eb@YKTIx z9+wOMH8jC%HF(Q9waW@2AxuJI39O&kT$Zox7J{C>sM5sXn65-k)@YB znZ<@D_r8Dso{$bXlxbPJ`?l`O3)$*<8=+7&7`Eh&G-cF|3$(Yl69G9a9}~_|(5=Ei zONxK|_>t5+Du&fp{+p1ZXZgU%Js1Qk4W-_*96F>6*zs6zX>oCQjyvR@KD3|yri|2v z8CJ6z3~B^KwtO)ULoLx(6gw#|9=zl}*qo&lA0PjCa&DAk# zInASwG7n;@Hdl{bpI;$!tNnHHA@3Ef9kXW!>pNcB#wveN4`)Ad;xYk|S;0bi1qJkx z#gc!&Dt2(`^4902zoX>KWtw2|OakWkHG7FM z1%OPKzD5Q{m9wy_w#DJ}jnv2YE^ij-$DKUxpItY%!lV1^t4tHPRK)VqLdIl#{p;7e zue>?=%_Qp}O+rt3FyE;HOj6tlFS{n5WAO7om-6?ny^r7El1p`%Guy(zFM|n(@IDc( z&em@a9*^YKB|blgi`e3kb?3)AtlV|#<8mkEjB|mirhuOXm910rINgU2PXqUV89dNP z{fwp40;SK()06D{@6UJ6RoSQeLS4!*U_Yp)yq1b80?o^3(wv2RPg3D9;`ocr$L%KX zU0f(sR8+K)OPG&BwYoggr)t3K4;TC3xn%6hXMKf1BzF{K>&M+E|M>4XcI?<&5&M+) ze=^vDZd=41lxO>B(p5w~-O6<#BRye4$ooTIR?aO0*;=7q}6i({_uXJ}NW zw5q`2-y_n#XvRjnnXi3As^2X%lg-s^N+~6M-a`!W!1MsnjWRI@M`AIf+VqP7ASO{$ zwk$)yq}Ap9j2`9!Y-@$Ci!Np9^By>G7Sc&zpGZ?Fw3q z>HYpWtg$i`=S&-lz`E=iPbQH+l|lh<$71e3co0bU;KtYGVF1aKi+kgjg?mu>KShW%aoJBB{0JTq5-{s| z2X3*|E5~`o>4*L5KOI;T%wBww)p#>j&o_T7sNSB(%#&h{#pAQVfvDX`z9UVNHKexx+ivKR-D89e4 zs!A4K(5X|yh?&jRT1So?iI%SI`JY(;aX`vs&B28xIA7gzOYnH2F=ysq<8{apvRG*I zF_X@-HNBxtFe_6!jnvfDKQ!BPY^f^X`2e!@xF0f?et$>Ue{1*_bV447SrgRxiV#;A zj`US_IMU)A$DSN(qg{d7+M=F$hQEdeZTqVCsZERsyVB)wY5Di_<2I6 zHbS=GR~qDYSxwE4-(rW2V0&Po-}BhWlQGX+vAKl>GNmT%QGsJbRb>kZKOURW+p_uT zc`qi6EINkat>{5jc&s(&~Ca3E8$d--zHO`ha}!dEQGHe>2l&0T-H0W-U% z&_DnDGY@3e*px3q&3dyW_7|jHm8qcyd4hz5YhmFr1Dh-LSA}`Ct;j(9RGH{0uf_D= z=9&GHV5>X~2PS($f1x>M$`&tWf^r}@$q=~0A(%ygUOg@#3sU4=wkF2p6}But!rD3^5;}1GaL*|4 zX#_Z(AJ+(!?2iyK>X(@7fhg`m--8qtd~9N*A+@~EOR-N-?<59^`h)^=ZB8gY&SU$kNje*OAmc=*vr zuCDq-UMhDE8yz)qW%5m~H#mO%!r+Z>{U}X8@-FwA;TS^4} z12p|1wVAu2sl#ZwLTilivryM3?!*C;j!Cx(gBaY81Dn?U72h*3b2l|)Y#H+#l3|9I zVN-f?)x80qpFb~3amcj_U%g6(G>cw;wJaWP%1}vJWh2hucqfnKiwY5{k+>jzXbGv#uhA_G`!NCsCq)xNh?W4f&D$ zXd`tch`NEK+RyZ}=gxuZaagCe-9*gQz<2qbVyI-D`m$?wVL@nV`86%pKwyLN={=Q* z8N-(BrY1v8Jw%vZV_JOIgj~e+3$VMB9GG;3F*Z%IbG*A$-^LxAtdCcPPIs|q9=yYh zA2?{zI5u!ULurm)-gztPV%#>-+0*y#-D3}|-KcFm+kj0AOWk>L4G1&Sy-h#Wp)0?~ zMMoiFhmE?JamqFdP}q0bH5FMZ2qc8Fx@`#pL)Gb)`i@X|ot3QW!@qw0dSc4xDiAsF zvFF1jqerng8I+MhEm?``egjgiZ@JRPi#r@qA=!gxRNY0(bf$G$e*8FxuabvVc$u+w zL)ng2hSfCBsi@Yj)x}7t9W>R(rXoXVIu#J%Bd>qD^u=Kq=w1&J5uLEj=i;7hUkr@s zfqY0C%GrgH-GWCD>Y?)+mO=z3arQ=bZS~d2%*@=qb7y;{_l1!o2M&CXu>L17>p5ER zhXV%=1knB1475pV14&O;{g~N7U)0uR5xDhzLSKQFh zU?|dA9=u}JYaWIQe+@t61Sa-CXMPr4r<(BSUiI&raU{6{$K*t5y&2Th2FRHX-CCMTk@Czbm`{XU{CV_C7 z_xEwYx$ULM#Ihdv0Uf`|JFKg+U|bgATv!kiczSi8-z(X-F|6`DXtJvQbhg9XB;2n4 z*|83iuU!)!j6ye2&#wV^%e_0js<_8o?!|!%*qjgG3;Jk(K4nwm#T@$h0@Cnd5g(N5 znU4YykHq~8nsq*bE_SD07#Qz2);U?TaOaJ1nQsZUx0yLOI4paAodFoBYB0_*EM&kT zI5{9oLTHin3S6#7txUqRUNMI-S{n29 z>uGNoG&pl02SS^WM{{&~79)O>uwluGig_d&Ji6l<{piUNDCwjw6Pc*XFMAF3Q&fYw=hv3gz>Ldw3)lg>vF7nY^Y@oiZ7vr zZmlJr@A&j|%10K8$$An*Csiygl1L3VQNc4MPV}v(G(M8pxQgP^hrH+--b2|ZwU0am zuIf`*SVjgF<>$@~`}%S@+6t|KC}+?+(;em#piYDV zOp(K!)*{&`??nA|qv41Jo{9jsJAO0zP4R*id8P>A5`kjQss&!mwdgS@z{{KULDGpN z8wC~Z__YrYar*jNan6P`i@ul8Hs1D`V$_fK)G`1$DO|34__*+$hcE4YyNwdd8Rd?-ayc9G8{1KBStR# zxU^w#RD;FCSd*gKwC9x8W!A<_TUl~(==aHHU6%+l?c)T+jKWPu;eJrLGmkkc?#Ctr z4sG1FtroreGOalTdZMC4%E!gx7jL<`)rMMf)G)}x>!_>~t(v6>oN8rFPce~iY+G%j zCV(dmeEVYNN!4Qi_S%MM=pD_>sRc)p}xA3cQIh7#b0nw+(fT5dx&@!vLU+Kd<45U{>y@ zPoK_X{DgY%UR}w$^eXh4-*x7t676+Z1XM8g60e6aTN`f zm`=q#>s=OT$v0zIAg2!lgTEomH8!~&x(ldahcU-1_G$&;wA|XgaQiFnty!)tjbepjc zN(&U1g@4F*?qOhvhK-q#vE|dpkGj}8y{b?T5EXwwfVQXH%zOkXS`3-#Q-@mwL~hP~?MDeh%`=Zh9~R=Cu-6b|Ma7 ze1!j=ma&EMa}4F<_IqgT*x0+k(8ETD&;3_^yu!%M#&(4aO}xA_;6ox626m{IJunp{ z_LEX;m)#+R_u-3%jn+`iPOYYKG1!km- zEl2ZDkorwJF{jMw;U@h*HYlBcsSnM0&f|cG_y|*^&2tUtkzXnHOs2|7x-XD9%9UOU zfZQLT4G4)yTUc25*y*rxZbjUQ0)DrH0WSwD5L(Pd;y<=xT30t5#D^jBm|Xbt9}^Up zC?K!K8M4@b^5H1bsT>CI#a_RB`N5yh__%<80`@H3tjnz|NF=JYwRLmB0_J3>sj0gx z)ZotH0V)rmFsauHX+TD)#C`L?$i^gYNyHbVZUm;-FXQ-ub($4Gv~=kb#{MFqaN;eM zB3@$?6R*%JFL@t7EUDL=`Q_}{vyxj zohD@CKgtxd-OX(%3UB6`WH5`p8?FTSybwdmp+eyN*gI&HO?_Q0p#EWCBy?MqQs)P- zGb9v=pUA`lV-Hn+Yc+pj95I~(w<^8-ov3rwgITOuW(f5V_@rB=6nFqav@z}5tAAxf z40ZM6>15pGYg$^={_2(sPFjyVzOUNH0vMGRhm*aED8?nDraU1|vu*231BGf$me6gg z-=AAAGU_7We5yA5+Bq`%fCC9zs)5}8v^jp14&c9|8;hM3fiw*%eG+G^`ybJ$%G|#F z?ng4Y(74Jp?NT~oeYSl$)CV;Y4IxSneC*tmwO#AI^^+K6)rGOA-==103pUPN;%F4OYy?lTGn> z00Dhu1`R;kqsNcozyeA%6~NKZ!_4aA?uuaR9awn1v}IN3%rWTqbg_II6}u>3S4aZU zkeUlILXmeFX37Wh5TM9F2(Wjp`G@ndbUbR*Yg!jl&&R0|mQA8E^*c^C1fq zR@%r0RI#Q4Crl?l?*0`#a*6uHtzRaN+JsbMzLPG}aUdqug81NB#bA@Qje{gc*&>f^lGO~waVx!sl$iI568Cv2t-vV*BPj)^!>#zBxV^%>SRp2r$dIn9_keo z1g#e)%vP_acnt}Nzjcra0Q&TE7Cg$uNH+qP0htw9h4nlY8D(KkzK+WT}Y zfM7D+`;f)O#Z&6J8?tZ!b89t0exUm0r;_Nh9;BgaLZ2vo`RS{a_i?S-pljlTm<^Co z0gYzMYOo8#G0F4L47ZMKnL#B0j8nCP^v5=^g)+Qe`1*!@1>6af83(sAiyDLvgLd;} z`IcayxKbuC>wN;$@5lLSFaq8ST5~0QGe?QO+;Wwj8)pzXCzsVBg`qO)OJ1dzh#@6@ zRsip^Y}>X?s^``hvrj*GP!+EOOtAH_90Yd0ar(5&4@+?G5S0S&W3u_D<*x(D$x8WU z`~j3mMu4~upb9G6Sr7Q2be704f)M`210h)7;>s}%4UH`-HUL`HFP*qL`ugtbzVHM( zueS`~j$*^$p(GLOp*iT;Y?kLtAGwJXxiW%5DJdT~lK}Qr_N!bLwh;Jo69+P-3dA7X zzZZ;RHZOVd;@Jq0MaG^y6RWN)RGlc4Go0G=gR0T>i|KZ(rsjhlqHxH(KkGC3iWZ4d z9ivp+CMdyoZ{2?ZV0T9cwvC2E)gmnvRUN}9i65Em_cl!&XJ==x(!glUJJdotx|KLp zjI7}8@)z88lLLlHm}67Pva9YhEgeT#3Mp$$HPw{tRd8zQ{l1UlvQlvT5v6tQ!I3mF zb75#L7+^Zpe*<2fj$I>qqn@r%)f+l9g_F@|)jSuXQ|66n3A+!51SxaL(@PLI_)M>qy<)zS>?MzI| zsabIZ9zvXq#61!ABemxvQEDs(>o|*h3XdB#rkw%kU>Uu*pR#un37`*0AELV!0)J4j z4E^3XYs#l<>0%{70ujzdrt36F5h4H%qa&riLV~7Ai`CN5xqzo)3>&SYB=F$6_7Zl8 z!LsYq>)0iUc8+0c^}Ax$+*(DuMKE7W5F;)%6t3c%?9m@fR?V`qm0XQXm|(<zd?@q zhggj?$3XQzcJyd(P{Hi{JRMyI8QQd_q-`4`as8TAo}Enkw56kr^#RX*{umYz37kY= z@ozj5giRsgyx7WOVv0fi2UHK8y`E1c=}-R8EPy+zq1IzDXUx8C_OM4GdccSv=O)S5 zhC^LeX-6>Ch{lgWn;aXwXg5@CCJ{Sbi2U>T`y@{CzDb}I08?3DV%>hZv7tc;GP;FL zBXN)m%P5q3v_(pQOr+egbC!7G5@(k0f&z^CDdI-rEJ7tvKmgMSw0zp-Oqdf}y}m$- zMx$$V1utrS2pe^8rJ5yV|KOb z*U?e_j&!M#MxVtj|LobbDu6zWKa7rkUaVcg<2?7qUb%85t4CEILX#b4Er|QRce$n-YY1z{GgQ!=%ok%zpFN=n`qOCq} zs^s?@!od#>T}ppFz84*+ik?}?6vcyXbVP3z@NBA}$}+IBPFhdEvJivDNELHS`tw{- zwAdtH&igWGz+}MjEB(C+dwF<1n%b?od5FC0U${5|^m+4BJ!zQwiL*cShranxEb-?m&OQhfO;S}zqsl0d~iIK2qRM-Y;5 z6R|dHR6Op>D=MmLYCgttf z<~I~Zp>;uXOWF3h44?!?4)4-Xc4RmH77%mtrRAV(0ddkW9YOJcfmDFMP}82E>+D?u zo<#!t(A*}>J1C(V6FoxS8&L0f`ay+wSFU0bqii!9AlM;coDy@O6nA8~kfh4-Vt$!u zF5diJbuF6<{|Pvj%yd%O7)`R47kaJy&<7`4fiLqO`s-%$U95xs7T6rr-jU2lW)b^D zSnf@<&zAgNEhGxT7m}y2m!8K?Oc}^|{^g_I86V0Wu`v4ASg&+PU?8ZEbI(1V88e3f zBFZ{C28MX>%)0QS#{gcX477vVm>p$635?iLFhp1eak=LOdka6+dTJhPceM|WG4!|*9REW4OW#I}ZU}6_Jra{CU;t3EMDeHAP zf$KtOkf5$dEgJhQAcNQ~bsz*gCZ>$Bb>c)0ut7WpkI5_o245A?d)bzs1-tWX%BGJ) z(Zz%L(?JLGCn~t9`Ho2p ziz0JT0datcL8VFnQvCFY_|_B1W)LVgdsok&Q8&-}6y8A_{bY=c*km)HE6iCJK!jgR zsFanHdxddB`6F>W0S`Ejj9i=H$r{4X?btD*K3r&utXKFU!gjs<36aqG?91n0423?2 zevy1Ol>gi@< zD+Peq&)2sBm>GuxwpUfyxlV^~rT72@<^hjM*jtVRE+0>0MXRUk3Nvu3N@E#n;3=@j z-5|&y?j7OdW8>n^MB0IRBMpvTW#?5vBJZ7+WN;!wF&0%Up4aNSy0f662xUjeD^yOx z^;LCbqY0l7CMqVT{A4Bh`Yj zGbiH`yr^`B35P00-{LRy#2V$9(-apo0i2A%TbVv~PD~EQxvu6Jfg_Mb6^6={aA3D5 zYXe%74!OR2@WLea1|)yZvWO#Fw3ksc(ek-Z{EqTQF0I1QCNPTU`Ti4~&7FlD{w z=K84XeVY*ZR@!aJ)Cx8mci?@m_+x_7L0qEK8rFi|Y)X9vNe%@1fOilMKi4xP$;qO> z1%ouCh{+cHYfd^6l*=CyqLYy8LcHOP5Lq=8SL-}p5D^`M=2o3q3R}g0JWw+R88?hO zDU@>H0vh@dOc#{b>~HW9RYekcC9-V%$v*;KX8%*v{r8hqtDO`k2hgjfKY&W-#Ok84!(>-ScS`#pg00gy z1K%IB>e56yN;s#umL-IBL>+vZK*}{xm<^wuSUZ768C5S* zJZK?B%?MbV3kG9^;2JpP+MdN6NB(_NZNcyJ*k7PX5^Jh>T?%ZH$90HoCL%W$(;U=Y%NbXC)OAu+2ES9B)F79Sae98f^}R1$w*82zQ||0mJid)UrFrt1 zCIOv%tBbz2lxL<1QOt-_&Lp8oup?VQpb`4X_&q9S3d~r?vrlF0Q+#yrA89TxMU~$^PVs$9 zhGz2>(B{q(50B9N+I$qU^&2+SHZ?WHf7gI?28L#-9Q|rqid`~)aN_Pb@k5gn`so)> z6(bUiEGZb>$3s~`E5u`23}exU;b-tWnYUna^wQ}W1BMYvh!V`!FXe;Sc%w_OjS-Om z>D~a}6y9)GSc2E|j|}4@p90EME1b^Hu!L(WD=RJC02~e|*7;r7M`7)Q!jS}Wfx3-H zui$8H8trvxH{4*VBEZ6+of}|y^5DfgWeL~;uOv+}B8rg#J3AIGR;t1~zMDgit)gT6f(>fNk|5luVfwvAy zKxC0wb6}$5wH!ib?sFP@1G)$J@lrQGlW_k8wh&0wa{o0W2e*wmwu*%N%RmeXKmv)5 z9}yyrwwo0K=_lPW>M>*!S_$`lf?*)x%Lqk^UAuPe=^agqkaZG)8Hg@~1XD^GqyI2D zkCjk$8zg$Fe(_6?EhD7Ub{Ks`h#mCv`0lIyi`al(;yLAl`vPAw0m2|6G3WJ=t4svZ zLI^Zs&4BL|f*k(rFWF))Dla`*W$Pba+asaju0s|(pI9wVc zrNKSvcF6EWwSM`pkp;WqtzRlY+ds;6;JttkI(U@XxcHb=JK7iV|)5>KMXz~A&}n02}S@D zST0}sl#tN1FN7+ER(No1?CIcC*BXjdGC&b3)-Xhp{P+J=1E3t?=l^^aM1FTSzUN{g zTFGJT|I}@$Y&wlwDchYw)A# z`R~*C|4$h3zxeH-F8JE2HUR>9^yG=lvWK{s*dqxKcSr>a3Xg@!D=WWQxl%GWR`>8g zAn|x%DD29gs*|`3O~9jwh-4H3vW{Q{t9dGvhK{$Zl_&`?9wyuk9VSj8wCpmO*!2U6fk68`f?gMiwJ`bkP@ z!h#(iA@s_QL6LqAJ_`YHUNBb#KPJpK4)qv@C9M+*ZSlZs_0MjhosWlxP7~r{0?rP? z*I@0j5Jw6&E`*53Y7y*bsrYK`Y2a z>uqoUX9ZM^!iyD)Sn$gjkvof=UpMuWog3|mD(Ddc6rn+Q+W6u;X)F*qrvZFEAwU>W znAzqHl4NGEFz(#9f4?l51o9^F$j>e;{H~NJ0m4WKA$VO53J5e>8(_*+12J(PBVU=Z zd?UW$9C8pw(E3&#o;FQ@i-5GcYuByA?B@K!rRMHOGa`fAl?p0R1>@}06477~@i>ID zjmUNDw+|$s0L&McN2bamLFCeJ-wx*M>eXbWse=bNI5@eu6u|)iVvXW7rrs%d8kbld zO=bu}ZPhA%y&`A_I^H8tRe(3s1jiTpi3YnFLXgIwM?Gd*gwg0FWF?y^Db;Z|&g`J8 zN`k=kZ$eI@M;S$o!E5^{Sjx1BTcWm1P8JK0fXf61#QvRmH40CU1soy-*;Hc9K5~dm z{xW#%8{maKJ;4W78cmb^V&5uWg@E}a`ax|u=sA2+gia>dz?t2j$KNuru%rNCv%tsS z@38ng3V}9rAVbieCIq^@NM_BWrKQbsoX{o}PNWWF3Xy{XmTt-7$Lwl88YX9!E?^CT zB1xXo&NT7m=vPuj1`uJk5-}ZWt!|Ni3(3!0z4u^v#l&`WW7D`CpP=NziAql7z+lpv=!S?l1M&gm(Y)|*INopxD_srV8l)@kwf2Fw^n9{r!ytSK z2)0bYyIlc80hod}`ZwC&Q4rrq5z-3t;aPFs5Cfq&G?unMyXMLDA-_ai9G6_ezTp>{ z1x)TL0SzJ00G^LFV90s`(Zv`i&&Mk=gn9BPECJ#m%q|hK{1J8>IdmKvT)zF_P4_Cn z<$-OV!>noNbO><|579!3|B)BGLE0>Qc)9@R{!x!yk_Cy9geO~bc1*}cO#=2bK$iIy zQxffQID!UOdsWZF5y~lG!oX*EY_%kUiF6fje|E!GR{N2ng-l=##OlLi=L0P?eu+RC zI^UcaP)*UH9XfchYQp2h{~%K7<+q)^BTmHC`p$z4WZ+D3Lma^=pc}2YblsgB9-K$J zs(|5K6}&d_o@2E7OgemAMjh4yxbX;PNCl4ylq++{tg*o3o6vLW@Y2LQO6ea@lxs#M zOrEK5X{=5}uR!8UZlBK7Uyynbv#ixqgJ1v&b9kQfvVE`sU!gFP;Y?>ay$0Xz7=rD% z=Z4CN!XD5ni?~a3cZvxoY0n)xT#lAV6DPMU!@4VkdV5d;oRJ)6~<@K ziuK$*%ll#^_ak--IADhwtCcIOX;U0?tzl7cf2QGzp9BtPt6xFvE;a3TECz z#WY=}*v82Cg6QP&dy`h>yzkz`fn&$u zq(43Y_+7yAmM1R%6=KA>H^_yA#Cjl)czJ6^yr7-a7Ofi0)Dt;!Jdq*j;h+#Zo^6ry zYN9RQo^H$We&wwaDO-i1hl?SiX^?8=AWA9!5soOa%u%UU$&xiiAU#>Uq~edHYNt75_x6h zs%s#3E`6*$B3-c;4=b^YAlIM@#B>}44-iznjZf~m&st@K-!fcM?IiaT?Swk`2ZD9s zCC@{lh97!dH>~S$N5+fu;~hi_71N0ph$xB@e%L5Mk3(dIAZ>`$8bBKK75az~eV6HN-FxE3-wdGD{K*{|C5&L=1#T?ofqjo+vo*&0CwDiR2tC80mBp?q7)~ za;}1jQ=qER;U(jJ`DSu*vux7yn(1&QJkL6BQ^c2Z@)F?9BwNo`6~fVP}4vb`pqa!(9k` zB%0gbW9jYX#mUY6p|=2trqp+AIEi?Sl&4-Z{7=Ft$Ce0jR}Hejfj31w(eZerwulW% zc%}U!W(a6^C$04$MddM=bAT#`aGXv3u#D@Go(b*V#O51tqIbT&6i9?hYOKQD@Y#ZE z9{q9rq~po2rEZ{aq_GI>FWO-{dHc}&4BAgX)G@hd54xPag4WhMcMA|v8#cxxFC zo{oCVi!5)J7f>cFuF9CdIBDZMx$G)E2`8Zoy4Jus-ya?H?<~mJ2 zHqm}TOGer9`KlnxaqE-dbNIXN=+Z0SO1|xcqO%BO2y!ZjC+iW@N9n_(Kxlb1KyiqB zWM(x+C8C~?co-bq_aib}XL;s0T@HH^5+w<|Hir&HlSszedGLW7fF3q#Q+Y7&^C)WJ-_;r@vl!m5x&0jS1G!JQ=q@17Tn}){ZJxbJ? z)UX;1wJ;g2RB6)X|9DSVRqeeSX9Ink%D!VVr#htn(pAyj_NZsK!OQ{Mzs)9vFKz$! zGmkfQ_Cwdg%9C5+b|s(k86h_RYr}?qVpl2Oh6}0YoG&da%Z59CuwyO7h7B8jjCat@ z%*-H|G*>wTI?S@+3vESCQ>=(7=F;CM)rxsgB3v5{QF=)uI7@O9zS{FfZ}@)jBS_8Ijvl2)D_`pH z{{4HV!Guvha$F}mde0hs3py<5X~_IJLh&TWpl=YFIL5eryEinwHMD{J`Oe?>fyuNNIt`-$e&>yot=GH1T7({T7`PZC8BV(@}q#m~SbHylHgHlruK&g?FvPs5Qu3#!$lw1@OMAAU2ro7p$O^o5| z_2whA(=V<;!1o!geol2v0XIN4OPX$F5%EOT4p}OK`toXb!TH31?u~66|Iq^AIcEgs zAcs%mSrb%^vf?|3s=A3E$6XZq>^VT4Kqf!}DeZZC#%dd+*^z`gT zk$xbaz(_XL>wVLJ!4=)^^S)9z6D}Y#CbKPT@axxSO*e7~EB0z=EK~V`V!R|sLa$n- znb}!MJYJr)hcXxg!@?K=_GI${*zzAAzuM8y8Sv`Wt8u4i2H(gFh#fHQ9(TY%DLDxG zA?Gl>1BROiZRnDNZ_GFYM?Uk8PXq-nm5Ncg(_y6WFXGAWm! z!M=Fq%AqS_s?wq#<3HBceEL*@Kl2=AMXCKVUjQT5JMgTYHJC6NKxPGr@8=|hj^(OU z40)Kbl}VbO=L%t479)=$a>r@7uG}r$OmwclN+~F=ySi@S+aZB4ojZK9vzaiX#3CAvMjD!1A3mIxF=7+g zemXDlbZuQ-C4d>v8bB_rX#5}I6LEzoCf+H;U^ejW+e6w?rD^<&!cd;k0_&R48JSA!d~ZQD&??FVTeio`lSIkqF^lu6BF z>vQ3u!NF|08d=61FQMv?9Gv`8{A!-R;ZPBd!L7IGxJZLpno+qRODp3XBlLl)&-EMD zD?IYsKruc(?pZS;+Dk@Jlaet_T4;2TjxCv(oZ-MF1Omok2SnZ-LezX`(0<^Y(~~K+ zFvYJ$1Pu9H(8tqoEkOMmgMmJ?QY{!D#Kw>#1Aecim!6G3(`Q>(i0w-yA010+OUwEB z>xzvyS^^E7%$V({pv0%TI!0FIg9~CDXNc8V@=yWp!kIj}$_=@=QM zR2`#DChMgffKHnFOYMv;YlkrS76#~mV3P*A0$x`uP8s`yvHPXd?u}(0^v%H_%{g{S zRxQ?UC$5YyK7@7-$1jJy1yiA_hjKy_u-qjF?SHZ!-Ol!E94~clkvBV4deRo~=w4A# z9X>mLU&>ojo0C3p9X$9J!a2_xu8eKB-oz&)Y!2B8D)*goGm6PZYU=G=uSUig&TNzx zy`iY;GRRM}bUKgZKs-3qhavDJW+kthgcmPrSpzo)i8tYIQp|%5Xb&Aa6eCR3%YN_U z_<-b;f?dnveKBPi%|{v}m3+uNYFM`fWn4sYX+=r)6jZ+o{tqb(^u7ww(n1f_FB@(= zLwvxA5eY0>4b&oOMVDoxH$6BJ%5g!?KIDY)h`JwTt`&~nV#_dBG+DDzt*2k%$CONU zZ$&&!_fwujhm__u@80v8(PQNZ_8s=U4EMjO(XO8uV7zJwJD6D+8#sSVZ;f)a)5Q-# zCwh87P7SA|g!x(tMg|4#b>(Iwc&S4;*2P%XyOh1VMC>ec}CBr)~EP zl^cN5&@eJy8b17^Vw-DOwT%7q+nl%$p)pc>R4M6rxn5(=<5mBp@;z(-WBI-qQ%6Fr zFPM57n3spHOc6VHiys^+ghHM z7G;{BpUZDM4}i2!e7IdH$CKfzGxiD|c4N;TA2D9kSwSn8&1*ayP;ThGDU9>`!h(?fMrn4k z>zVmloz~t?%iHk2Omn}vzZLbB;TY}b60tj<*2>s3G-GhAW%bWl6y|49R#wJyao6PS ztuFi)?@qn#f(FR|-&OK~Vs{wR*kJBTsm&`W*nFVF#D(Oc)z8cDeYl%^^`KBgOfJHS zPmyOYP|m{2%F3Ji)Z<}yJB*V-;@jhABq>5n$@6AwV?)DriF=FOa4ahJMoLN&JeJKx z_B=@ZnCy9*F-p?_Id;fL5=`w^J7akdc+9+i{kn07hhndbP#y*V+$NreEdtm`N}-$E z>niqKAN!(L4LWD(#f8(-v;7Ywxn5(+;8Ir}nDpa!!MRwTH5~i**Rc8vO85MRxT2bM z%MPK2e0SSC5ZNli7cN}D5(pjEdx*9Se;1MrC=T0LSN=X%ZfW-Wn;1j1VmGxx2}t#Z zKWUfrfgyiK_Ced;()BU631V08zGs@fHivRc%XQ$%cvW?4tA;thccv|W&7-^fhbz(( zDQQ-IlWh`&v?)8q3Tz#ft3vj}FxHwwVUxhaIst=Oah#JbS~N2K@pKyMzC zS@FPn4S{j8d0zwC6W|}{e3Yop6@yD@lUXS#-A7&+S5YX&77)XqN7(o`^8IWUCiOb{ z8{UJWPls+kCbOu_)E${o{^-X?iuD@Si=qq#AdZhhk};0rE2c-h+9*Uc>l_^&wZoQS z1gLp0PZgVa+uuwj99`m(7+c)VQwVo334Fm0?o9Fm2b@swJgJQ5@h-2xNc#-N($Ya0 zUq_Rbl@;i4sXF@i_B$cShIFTCmQkSY6Lm4H1em2AM|#OWF9>OrNubYjEiElWB*8ou zbxPG3B=;OmXbd^WQbf-F)((RdH@LGBexO?r zotWzl5rO{$PonUX9K_w`aOE~(b{ubyY4aSz&@36PMj@O1+y&C0uOlN#bJHWZ&N%!f zNb34rBA_VAL8zZTc=}h#Tg&eavA~YD?F~`;s&@3BJ0K*)2ykv2*C7X|R}ah^k@0XJ z^x4J`28X2|7QN2=P~&7)N+lTL*>QQwBj=HMi)Gp%e< zF*--nQ2ZGzYC2#C+#a?G8+=J;JSE$CMWNI91&w)nc6P|IgfL&~_(LgW1WzLoz~m^u ztW;Orz2mU{=nIO7&-UDtDUtGhRo)IE*w%Z4O~42 z!V|yu9Hu~kBz-U8S)mko6e+)(JoH1Oqkd3F^@4e5E*QrwmyW9qyAUVsPKx(9+X*O$ zq-k++v67oIU>q-6UNI#zGBpmHrQitoN=Tc$YRC5NW$$OCrM->o&Y3gra`h@V>CbiF z0HDmQ*tbDWPLAMT!>QdsYN*|444)Pt+9CnC_ zULq3|EC=*lX`g_>aq2okMWgxJjB0#h!Zbjk#=v8R(iVsx+C}1_jx?2)vXx zj#6N=IBkew(SGO?OK*(*>gp;pha@JRKjUAstgh}ZcpTVnoG&?w^}>XiWqMb{*RK}= zN65|RRsE2blOqQ~`dzhn0M&|`4~b5-c61Cqwm+XhX?a({*Ai=pR==~ByZZ_FIrlue zddi1}8t0v&8q;&R0`88n<*;#ZU??c_x)e#$vAIv@3+~x-^7=4)5GUF*vv+lM zTR}gT4z63bE(4gcmT`gI5H&z5AAstcLfXE)7sU7&BRDHHg2`Lf>mt`}s8B6-dClmZ+*~0vjXb*UINSm#)}v(p;$b|% zl@Y>ro;6E9e~x55`-C}#OC?i%eB#D&g5qk4*a1;^k9%+*m{+d|@q^CTLD8DhRPBOC{4u0iR}YL~ zBsK=WB64PUvNdHVqHaxneQNHbTHw~#%nE<3sVT);b$A(v-;~YXRYcz)iZ}*vRj|I$ zAQuR&)>ae$5&YLi3=2tg5@sKmG<9^gggH4mF>(n3D@Y0Z+$Igw<}gN^-dXs0<;WeH zAJ@|AU&1%WnUL1KEyU*U@Z`!Flg*E&(9YrKbL5|W}6;qoIzatP2LkkROBc= z?RH?v_mGsx1{ORo3i(dYz!->>OPY*xZ~l=H${Q4P*Wdr)Txu0Gs@t}0qXG1+-eo}BE=-a@81Hh!3PPn)0egqy4vTzkvnPyATh z+BO!joWcLXIXRmUYqet+3yab(*J1#Ep!p7c9wG=smdY;maCo^GAzD&hT49L9lGck~FmSeR#!o z2wJZ`jmPpe#4l$y%a|JAGX)hdA{%D|o~!HEukXQ~L|ucOPzV-Fp zMuyN{NDqbzuyakLcCO?55H~wLee>Z%Iy8EIAZ_>U+qVwss3#%X8}A9Cn1)YxMAt_! zZlfX{4NXkbwpm7i5L=N`x&awOiFSH`|6SiGlGCQYPE6qYU4Dj6!5YIk2G0lK;l2n0 zh!AyBej%Kzns7GylZpR_wZJ1qf%v?GGRK~^aXu59rV(Byvz1dOjQSk<(R650Q@(kG z$PRE7@sc`I9=e*4d3Lg{SOjq6;evqsN= zI-@aA`h-6br%D1L-?yx>4joT$_M1l7Eot%A4OgB@dzT99nm8f{fuEYeZW~`NV_(r;i0!klD;YPwz>yPqs=m>gIawhi!FBJfddD2iMyx=6*!n;B1b#i zixz<2{vdNW3`7kdefKqPz;-l`0Bjrdix5tIs*j;>LJA8`f;=1aDu?2trDXv5HB@Dd z=k9v&KyO5Q5hUKh-ZV5a;)|+SC1N0_e(~ZSG-@RR53e%3)XC)qis*xfhgpO^8(vJp zIKPFhvcVAorND{LxraKv6HzL^BCYgDXH1|M!fXT4ImKY+^``ccpL%RfBR*ENf`A3t zV+%$IuO!~wzOskhJUlp#NC@AkIZ;FdrT!#o-rbut1t_;{*#h~=dOS8qha*!FMhVV> z9%Iel{-+dDQ&X|R*O7qQVHLlhFpV7sZGK!tWHi1QlZr$Sby++7a@5!zWs%`_j=D`0 zE#nm+Gh8;4xQl&w$+|1ot_4Cu$F_g}-^3f=EMRq}EUnG6!~DY3vD9ESrN)@%OUfaI#H( z&~>!BekRQe_-P|M8bxEg0q)IL^g84Wt+`G|5H>Un3|_EV9W1ERICbiler@>qurG8QI*L>2LS&gOxJI-NB)Gn<642C~4OP@f()Pu)nLW-UL+g6j+|V zLnk41SxbgEMa9hojx^VK;xZU=fea34ow%Z|!iwc{5V05AH-%wh^cQ>@4&`bTJ*FVkl)Nf zoTIS#tijw`PO@<9is8eQ0j93+I&n-rO%KSZXI`>=O$Zk?b@BEPMtzs z+5mCue!w257@ra|h+-&4i7>H;xk!J*(syAJ2iS?9?N2F<5W zpJ0DE2&DZclvMo~=7=ZSy6^p_$gxSB9)*_EA3Gl=b*Z$nufC^!W!6+yZ?hPLmNUJ)cPKM@98i9ci?Ub-dQ#1F;Q)vbZ>SA`Cg9p)X>^_%;^zKbzA|Nwlt*po! z8(z8cN=;9_01XNoxDe#kW8=+KFu2Y&hVlIl{&==9LVo|ZmYwXXBndJ;TjUQ7QA9~vVc3!VsA zF@R$0?i1431<_8i(Ot9{rnrlrN498!NYP2e$>*pW-Y8S%dU6<2V7CuI#56i^Y8Yth zcH22Pc)faci1gG5V?P-*uV9CzUV+y1!Q;mpF$tnr$3V&JP3`|-7ai|DQob+-ZWsPG zIOqixJTv$H;2!L851u^H9?(Ri+Y90#{c#q}?%jcCf!QiC1o@zl@8sUk7B3C}2ks@A z`K4{gaPa4nN8oRJ9 z)T42NGFe>0?9wG46vt-(M*a>6`0!L_&6Lkh^&2v zZgA&ouvx~Shk^qk$Dsfo8Q)3+loI&Z<>oKg^nhaVU0Hq}iTTzDfPMM>&mA4T`0DAI znF8{<(qcMHoZsG=26FyZZYj5_N%NJb`?XU@k4Wv7+rii=@}TCuvdN5>UVdH-^`|^* zU3E35ZJWP*&++FE?$#v3vraF%Mn_AFY**>hL(PtJ96sPv@^CNOE|5&vUkO&|$1;UY z4SOObEgemW^8Gq1pn~ZTY*$yVE|mNZ(VWLo&tyCuFvjcPXxPn2#<_@Y9N4HB`boIg!>R{+ z%kT4Prc=OY%Mn`aIPqk_MnXaY55+TJ#&tvMv@|v4_uneWI%@>qoj1Di?ZOGFN#uj^ zO3VITE~<-vs{Tz=Q*#frGl&R2<>Dd&B=_ss*p>qaUZ~dU0|1*{#D1)_i|gQpW%i1O z1{z$;_Xv%+10bplqdsz*Z>6N9#7ONsQt1ho0j@T1lUUFK+9AMS!o+w5xX(OyqXm*7 zjB~!<*BfqQc(>OJZ!FDhZDYd^Qpn`?KRc7fP1iJCnpoNUhl`Of};-|WIFl43PYnB^yrZ%hGTXPj$VN2iQ3s*y1KewzkTy2 zameT{GPA>@qXU6V_u+}CZU`i>NcIW~?*K2H zhaLX=LhC=!B8jAq*ORg#F0cyt9`BTS@R_!US4W2CwR)SX4_ru zkdOnkSToNJ`PPu)tL3?!35mDsM-LeE^y%`i9%%Ls%|{T(1tU%QeRQvkI=kZPu?(ki zRoE)YAs4-80_Pfby9g7}e;isinGl4s7Qne+0fO|14=ov(LAQ80OZRnSn$)Hx zKVi&nz$E!>Kh)N4L1!#Pu?L6`gsyKnR!F^I z29IIEu#U4Xi?8Y7)uA)F^RZya1e&aVfyb z69+a`fKH%+Rc(%U7HobQkZK%Uy#)suYQ)iPrT|@c5doXifcrpgoKsPGcD~U2K~&Uz z07K7E0pj=TP&|0}@D3QYuNYjg{6e|;e*t(n*&?i@W zJ60V4CRAX6^XnemR|b&h`r`Cnpuv7{J8o*rz)^5TNbbXG-_G?L?&=trM3_A?O1Lr1 zf@2{ZgKS+LU`Iele^}S~X-QQ9+X4BwvB$;3SXe-D?`W`0NWyJLWZs|B_<$0ipOA$H z&YA`36XtM?SCklYJnkDi9QqiBxtWsRD`(@B%W4TJ0=55TJu9r{VVp`*!IHp8sIte1 zB556bWOR373Ti^~088@!^ntkV194@xsrP^ z5<2VKcV#@xF{33^p_qMs{dGqiU%0|U1L9KMBK zmu%PgW`_g=<$qi=P+oHX4Jkc8IJ)e><;BH|c)q7+XK$m$CX+8V1OjZgL)UH+4ulQ) z%3mHtyjr_nqVNg9rh%Yqnlw;aeqNZK-ezT>s3jq!t)tTqtDB5>F`~h?0S-DiD+XD` zFT~4~k7noOY}fxwibZO{M}OWnb6{-DA4t+OToi(OlPfaQC-VzS;xgY$WnjNh76*I0 zetv1ai3&_Tyx8Zt=SjO={`mwlr%XOWzPcXCL+4Jx;MbXn6|)`}LPN(LF>LWU(UFmQ zD#%u>o<$eVy9kuDCG_LRk35f$+r)OxwGW#rnGDYYK(% z792dgr8*N_WYe(tT|d8I?%yDZ18R2Gj%_nce2kbTrC(1iK=zyh@ZUL0z8LCaT<0uU zi-U97Tah+n`IDFfzhV>OsF)fX(;4kqh4@r4K*}?wr-Y0%_%KqL$%Tck?=T5$!CcZ3 z8tCiGxpCvh2iyh_mEQ&#c;&~5s<{qepI0(O%<7^nnvA@)iuyRQ*|&{Q%zLKkXoADh+gP2AKm(*aln)H65a>BK%kLCn^=EU)Y0!D?evn z@xhaR-ZsC7s6jWw)E9h%Fw-2NEicgVUW6YeIFLY<;B@(HI=*5m{EBC{$vHj;C0CKP zC&PFH{!HCS3u^ou3*AoUxT+bIke}PL?oOjpyTMYy^cr*(j*V5+bty)BjQ8DuF^_%s z1t*ST6%d68_hA32dHwov>=D|1Av4&Ujj>5$X>n(;NYz&^lc3I5EV8)szA*YAdtKl1 zC;EC2{Jso5Mjz zF<#!(swiGB*rnLVeqx;*bN>jOI9T@+)b_8LL)v`MIV(`x5LgqOXH0{@JK)2bC`Hzl zM}YI$Uax_CoDg#s>ON3=c0O#!j^x?b7|QvE+Bo1R3Ar+Fw={flt_IJZ5_ciU(dvG{ z?$X%2kNGuW7=Zn*Z8(4FdEmm??rk;jxIRNkXD-)*GZFp=MaRYqVEr(rTu~1c?#n;- zo3eX1S2YFz0wV`RiGpCQ?vK|q*}HPF)HDIMM~=Wcoai`~Q33KhSS;h^*y1zKL;84( zn4?uTyUmm{23P(@vtd*Tr@%e@ut@;m)V}5j!}oWiN@)#AwHiF4?14U>p61yH{6j(* zK;w8CrDtWm$NcMG+|#KXtC9Pn8a?EHzURGsaJS4(?Q5RlY6UyB>wl4+m%)hx!9A8> zESPZQ&Gv@1L~8-3a~4+|yIYY5Tu{NTh6EKSE<5}+P+d|-28=7*E6X0>)=QwKHFHJ? zz2F2KTb=(3H*$Zjwu#nz&ss`?BS6#2>nL{ku*&RTdd#55s(Y^hS|FG*d#(JLE)6(9 zpqOc$$OXZNjI9bNYW#o6Ti&+7oTH>v%O=4=6?jhWa^5}OLu+dA9&)yQE(o2%_>|({ z27-X$O)c!_KaHkxbDmmjYilby{+bK1ekX0!Nnf)iNzfjiMWU*V(E4 zN`RQI24+Z=Ilr|!a8uY0tiwkpFP>x6FLikNnP2}h?@24G)B)dOY*dnmetzxnjVCjS z`&COzuhB+AF*3Z;xq$vXcfh9MrzYkSwdLg&#!Humj4DxQDF109s~BDv6q<4bJ@H{b zw(BJpY}SBHQq1`$*3hN+>1j272LW_B^#hkMc*!$(T|TX$l$a|OlUMZW?71( zVSbX00&JbmY~BezmEfcMbJU|8tl>R!_W9tK)Hc3bX=B zuZEP|a$%a6JJV@8)Z~UaD|l5ZVf~Xaa6nBFz zWsJ?)W;gEb!EXILd=;K&yoC@ zGv#EwMjwHPrzZ2~i#PHCVNj`hV!N@9R4tRX55jDUQ=8vcRIp(@h44uZgES_FD?j>I z3{3pe%2fEl&|J|=&=sCY-K6N&1ILl%n4>{6K=#O*TA7iBv}eywW_~AS=SugaKkV~d z&SLxa#k~(v{YX<6uykp7RYifEqUTV^R(4WB;<0u3-XuN}+BK}3*4)CBbVy`WnQQ6G z(M9m35CQOk*dv*|BiWW53dJRC7i#pMD`jfnc;YLIFh~->B<_stQA2z*CS2Ur#~YKT zcIr)9y}Roz&$+T{dg+o9Yunt)D@eu%Y#f~IKJ565ZrDZD>&boJ zW(Kt+>S)FI#-+NRN#^6l7*(a0ybwU1K0V&nofKH%&@G>O)B zc6Z|9_U+oW>+A6F;;-L)AlG1e;L_Xwzjrh^SQip#m}kqYM*X)kos+}*Tp6zbYV6xwJf5R z4aK3Zer2z~ZpD%VqFURU6#}ux&C4+p?3s z{@XW=z5z9Ga&h8G^2H^nvLY>*gj1lE{ow6`yhASz@`V02y@ z-k?*6v(~tfMIC8=ZPt9Lnm4lQu&MD2gBPLjAe6f%B1b;(KS7m4{z zK-!G?`TRsD@-LJfCUGpwY+gq4s`=^v;UN}~!l)CnOEwFDNiX5Yc%6%|Uz8L4yBGF+ z9!D|g9IxM=##jW|=zTT`zB*CWwh?l=Ye6yax6S2ATTzC>#NIfJX~ZnyT>^m&o9j)Y zxjE!Gj!4Kxm+Ed}FnePZD!640)AuozN_ykaJIxDJ6yu%QN{MfbxfF?URHh| zV(mVNo)}(Et*$KJy?2k>v54$2%j-i1WyJocPYyHmNhsU=S$Z(6)K7tq;{{;KGDM(af7`}bs`Mbe#NP&C}Q^v+QQ3YDrC9F&3q)*yL8YwY-3 zd1ogxz5uu5FX&9T=cCMx;LDecR8AUvCKEdR1*aRdnkvS9WZP5mOQe~<{P7^6!K(}Z z!l9=$+KZ;h@e`22Rj1{pAkIs#Q5~3e2b|?OoAl5FVilWD*F{tA>Z%-2>SR+FN{VEW=CTg#abZDx1F`HQG6 z!;L=J{>1SK?16lc(qMfuw-6O1zKYzps?%kF$T$KaB8a6@D(l z#Cc)#uLhuJ^843=8M?kak(+_lk?s2qGt6N~KZy!#b_NevQ9D22-|CD`qK-zErF!4r zNq_}qAuXxCVbR&yX@o96n4G-I0MxV$^LR_Bg#GZgFU~_Oz2$#Xe8rB#=g19^16`$i z(go!@C>wyZZ{lg4VRNXRUIhk9ip6kE(toZ9(n@HEkoadi&~PuPce)B3lxAnJw*Vg8 z4opPjxMDvEE6+|Vfp&Beow!obsK?j0Yk+df*iMoiHBXJV@=}|6_*niqy=X%?oiAS~ zd~}EZhfm^of9A9-i6P2trh^>Raoh`7o9h@|0eU?5Q^*=z3|l_6d^4Ccw#pkiJG4$_N~gh#y(C5;Sf$dItGPm|KYI*izV zHtHVe&g$MXc;8aQ5R!QtC_M$HZD}NM1wD8`1WON#ULDBAGTkd8qCG2m4O1_sZ&soV zh3~Ws`ibi~jW|a|+V{IO@IpVRc&^aj*%hs)W|}%wfs3lhdKMB9#7Oi)N=Bw?ARq)N zg(}Ze=*xAf&>X7n-LaX0n=b5d0$54AI~tdeM4&mQN=i%r1!c=otF!T{hRE+{PCJR{ zu1ndNEd|||e}lHny6k}}c{wZtwBw#oZY?(F+wwRD&h>n~`p57i8}NE+pMASfJ7u@* z;|4|qWTSQT{pi=PH!(@}Vm^I%;|3)Anz>ey6?495A0KtHgPnLS|Dp(uO1X$-#jHq{ zp&x&35|9VY0(%SJ3*E<9^cMl=Cb5`Z(8$lryNFA5`9czwXOoN75QhHjsKWTMOKp$B z`|~sKTTp`?C7So2*AW^WB>Doz`UgC{w`uUqUWIAs$R&vH02&z~|4O8-1vMS)g}m*m zyaLH-{tzu;Ff>0gKAw)i0ou93Tvv@qbH*Qd%3!~TVZqBu2jE1Ld|ZA3Q* zTH|cuIP}Hr7rGnCa#pS#afEwuV%w23JIcoDL($K?z`$pWYZELLfAko1Uqw*VW>%PT z;jy|$+JP4z4YquYme`HJ%9er%axcy94^@C5ii1i}q-Adn#xr$C5TrPKhX#-31=nM` zWQpL*`?NlNxg7^+Q4nm$k*>6nsKs%-n;fdLqMH>FW)ovj4pAaOBv{taaPFY86fpF^ zNu!L?YN02HeQARI;u)gWBYJi)X4$+~0G)ydthIGA1YRuS&~?Z70vaJV5ZmJ&=;5wp&8jO9Ydutp5}Jsi9cX;7 zN=}R*+ES8NHpF14QFta8L=!$v$uVNFYL>W* zlk}Vhy{)Ln*n^BQhNb7^{2i4NH79V!eF&o_q3B6S+>AmN23!Iz@?W362vS%6&DqW( zCt{s#ifre@a(a#+sN*eeUItEsJ&F(6Jd|tsc{_IK z8z;ohj7$xO?fATW0r`I8Uy664U240oc6=i9KU#o6vV_^dJAhwlbFKf+0T3Gn!PY)g*o}lRHt#F4z zdqp_!V1=7GfFNx^Vq&`NL$`H(`s*58;tk+Du?V32+ljzM)62W&v8Z53xrtnAfcbCH zU4aL>1?@J(4++D6lORUO10{e++aLkAy}kVzP{TxnlGX1EIwlYGErI@Tz_eTS;e%&D z02SWz93sr{DH8SAI+zT6a!&J`k-=jZ?a(f{|Aun&bu00`6$5lS|;IOBf=LGm8J zYP~?+5>^_@LG$i{W8}a~2XWBq?MN6Qs#)yzdyX9W{?i!$fantt$$T3O-J!-4G(kz} z|LWA9f59VX4Gx2?5&^}&cqoaohfUxNHor2g+CTuTLzPJJB_uIu|I6xf=naS)1s;~r zr7jRDn%b+s+Z+eYdz%08#}`)d!?uHceh2q{VdxU}^7GRWwjU3iswyqo3PWgsv2DFA zFJBKl(Y)m)gF08prwQ_4LSg~VAGC_*?XxD7kF9s)BUoeWA9@){Pdh-k9nr+mGbS$N zxkX+XeLmg!L~wqvfSgHkK$y}+2wK|7-f8*fv2}HWyB4xN0nCL8I}q)e=f5EQkE|85mif!JnbsCind4r(S_mAA)@E%IhFF zB;>Rs#YG^#%#k|e81z3HAJY)dH`t!E=r-MPLR-vjNj>!O z=@~cn?{CRksG=MeY37oU*o|?BbIzHMI^cDB`hyfV0cwzCKu<~f#7#g_k)t)$)$h=F z&w!Quid&djE^l)6&k75N#$)Ss7Z=47=&5qqs)HVpNU&n5^X)%BefS^^K`5qr8*CkH z3ZbbJ4|}_dT^X_I0Ydjmo{K`s$0x4DCSjTqR;6hjB8krBhzdk8$=cbQ0shKpYj=L{ z2R|WYZf>45NJmG<5g5qCAKSi`8QnFXqnuLPe04g^_V=GXduH^#P9H*^ujt;;GQGuq zPo!)R??61DJFu{t3Fkb_b(>JHeQsf`pa9v!Rqs zbVPVnY2+@Ej@lSaWUzyonOPrJMJ2`}Q~&6y`r|&Nrg=h=(p10qK`Hh#Z$b>5zyVfw zAg2v`a_&?*pmqm5_ZZ2;9dtVHbee1D>0(1J=Hr%xYWMyv-rPTT!y2yUfR!3i4L z85~U%i$Nzi&;t{ZTyhs68w zNafRWvPw$yz$ze2CFw$BY-~VY-fjeCP@V@D!|_raK4G@OQyeUJ|G*Js8s~~jWNabX zZ1}eUEImS<#w*1+F~3p@;}Hhps8ulzmNSf$s$PhnJ9jR>aH;>}krd>N{7x^y5(2Dk zaN|HUXY>d(Ea*%$NsbL<#7}34q*cA1wT%rSGlXS7<9$^mboe^VL;}PE@MKtRGL8w() zD9-m(v7k~DbP3=%a3XacGc*n(u(-&m{a;d>a=8AB3^et>P=5>u1l1_Wl@MmB-3=L4 zkm2O9WkPm5aU5Jn%#a-qQar#*w-oXr%_zjRXfGez!W|SOAOd5|F|*E#fP2#OupJ?I z5KrN@ky}=Z*%DG12$%Ol90XawbapHIVQD1YG~fLXh3fJ1BDMqWTkzC{T?q?U1#Aip zVPEv(G52*f?0^O5$`ND<9R;Ix)*$l>wc0&C?}hPHFjL(HnP#yU9-+Fu;-`rc4YD%TtNV>MBgmMYWp=T9bo za&GE6<%m?JPA^q%>d34QfoqGdD9LXrqSQaHLm%2*Pq+5wbM@UtM(6pn0*_zYd&H`f zKiJt!sDRVO*Vn6o{lOl$iR#K05vjsUQY!a6?|&D*G?M7ij2KdcSvSO>Yq%VQ&04~D zkl0Ig9r6Mwr7Y+QMQsX*R$vUwW?gr8}ETbLznww$c-63FSsvqVxuj`WfI^Q)fX z-4KE%Kn?;vq;`okVKtC00MtoY`j063aPGm@=Q)Z-jd5OmCDQN*TtmYqrJYe6F#l$h zFykPxk)PM3;Z{Dv8%>~&s&7&r)7(0Dv7v#(^vt!!#Gt|K7#H0#Og0N1$g^{AZ_En? zBMp$pV%oBOdrM`wDJB}R0yazgy;%--wPEKwq67l3hBRf>D~pq0DCb{B5rwW5nuJFB zcO(*Tx>qAx(-%CkIJe&bERzb`rm7fW3R1ukGvPULgYASc zrUq9W9)1ZPb{q9&gs;6_N7dk$yk+awRnWkoKQgWRXwoO_Ehhg_u3N_m2PAzoHhaNs zJGh0FwOQk1-SUVJNPQ4oOB-x845+zj#?GbLPge_VDhi}9yx(BKdpK0^Ob3DEwY&E= z7b729SY8&g@0j7i@dUk%-ysH^cm9QiqJ?U5p-5w%p_@)z?M7vAuEBspEwrG%0rw?_ zNuT@-)NSM6l2BdG++~PS!h=TBu>at$5aRNwB>n0@5T(%{)y(UX$UXA7zkvsRUqA!p zPKDb<*yyND&v=ZINIVxmKNY|gq#ReXWGMRZs=yiFX0~(UP!^yrx`Uapb56k{BM^bf zuRfyfR9XaqNvT7nVuT`4_6|>5{JB!(#V=tUoi8KES>ZJSa&jnc(+Vj>YS-NKG+oJ- z^mE7nl=Z)W7b;ovj_d>URzH0D6u$(^vQ8V>hM}P$lC~zGTQe#H?vg*gf9tEL@-q1U zfwuhoM4MoUsYoO2)*b+OlOLY7{=Rhl6m|Sz(2J;`AZnx5RD*<=-%jEs*8bvJEO_0@ zyU5JlJv;;!KeZwz)nONQTwt15ABWGz=LHI~Z{1q!X%wym0vPIak%}r2Y4}u-bk>kW zTAb|>&zJP+(Ck8fz~8j^<9ChEFIJql?So!!o`{);243?u`R6BqbPMga{OpZfZo-cR zCZ`l{G{57J*Mb z36^vqOJ9X~k7qF53uz&}@SvX)@MnC^@6@=Um)pymp2l1h7*oby!&(*pw5LaDnE1SVos zC~96)j}CHwU*E2*AC|b+`y~rSaGc3dE5p_AJOANuo}!+dTl z4qp1A85^7?7iz}7mwMerRCLKihc_jPqyQ+o9rw*8xXhwhpDL>YA3+PwO521r!wf!1cGrcx~^ zUv)kS|NivwEbZA(STdP$2Oat-&Msr?);oa;a(h@6sIqqUPB5%WeLRt!F#spK|2{H0 zIt_AWp3)UGR4M;#mP4AY+x{>WXDyk3(cJ3|32p2g!D_Q_V~@?PfMTot=@Up;BT|qH zn;@qeml6^6I6D>J`Wm31N&(utwP!5 zIl-c8B+46e&f*!=Uxtt%+uGSB-^NO#3A0T)FUh%ijScQG1U#;iZ3@KRy)^g2a>eBd z2I_zTytwv95D_qd9+({GJv9(ui4LFOL_ba++a^I&2DWHS*zS zgr@9y3p5;V&v2ES7EsaB-j6e{a27cG35|=4IGARare;WPT{bC)D>f!Pk*b7Pir%|p!Fftb%038E>J66B5}I~>KSa2;ft7n9tVK?J9avmx zUSuTcBq_MOFvZ8*UwjQ}aNsoTmvi166~~jT$4`(7F2f3?iQjm-24O9{&bmgvM0`9L z7?ci(yq7m%fAIKmBbc(%ZWNkYT3cCJ)m;uFova@1qcrvUHP@-M2+@On+_2TLvcu?{%?!WZOtBjqLzjp3oJ_2rvW}tOT6P;+@;D*=ltVy zqI->;c`lmer|^UYjou4MRQOCFM5)G9!p8#%`)yf|FJHc(DRVMFa+Q{bhF%ohXvfk; zlXe|Cbk8n>5MOcQ4zI&uco2pg($&&IZ?|DlI7w^+IKG@)esNGRVFsQ}_Av=k2wc=ie{l=BD;tg_nY$&Q2yKz8rgR0{YmrCa)^I zR9#h-)x_s@vP@t|pIWo2e zM}2)jU+{SaSFouE+=%k!c#W6zgmMU0k)9bFiVS686}uChk&~M*BO0mBit`9OZy5dd zVNSe`L1gFYGiT`5n*!)3#z@*`xI^9_PTBN9h1?|@z94O<9Ff|vX%kO@UF-LL_}EOL zK*w;j^!8p9lxs^hP6(q6Do?&V>0`kHw)(`(HwXw(k_#YWdIUH+i>RUBW5FJVH+tOI zSfFVhzLBwkq3NG>QOpyZyp*Pi)^Bzd9$r1TmDzIQy&18R*xUTi>7(gyf zH{yg~-!4&dPVjWfc_3^NO{X({+q9|Ax%|z}m}qUjth3alB(!p58M}CSGuzpX#5Qw8 z7;cA8slk_6j#H;kE73&0EM>JheVWqBYEai#Av%h6`*!+R5pn8xP|~r%*Tyb>fA}VF z8s-XA7tA{eY2ASZINSHxc59o`j`U1MBS;N+bSv% z=f!?;v6ncQ0%glAd(BK3lu|s-uG`GU?_PYEI_ScwQyel?2W*Gs1O(J(3nZBk`jy^2 zL2Gvx7);h4Z~b4?)~*7NmHTdCVP|u1uets*<0i8wZs`|uVq;^ox&@)^mfv`#VhG8& zk_Rf5mTGKcQPFUljQ-)_2G95sZn_{!&{mA1Fu6zPk{WjBaW&rfmbDXfU=7DErM)Ve z{m(N~jV>PGm#|#GJ;V6@ldf+Z>kF@Yh?AzZh#<+MG6@$u$K zOz>huip6AlJ@a<3&VqALbaeFS2pOBFByGg(TGXC@f(ZwdVbnX`!$_ahC(@NT31;Tl za4WOypw~i#&4B!gy?k?Iyn_B(Ge!hhuB2;@i8tU|mmbvjNX@DX{XF>A7}UD-SBq}6 zEj2nJeM7bRx~Yg!qUSh>AoURglD%4^Q>Ap%9BGoZ&dKsKoH!|j&&tZx8u9Z zM`djL$M&)et(o9!6FpgV81Ng2L!vUCp2n)dw*+lYjAM(lj_NGR>m@B%*xIVm9NpFa zIaD-#gEo?T$40-?KHne^ftOQi5TkIdu<(IPpgRRQy_HIC?my0mL`S_CfSkNbp!ig| z@1L3|%{$s}V8T@2oBGu;mDyzHE(4(kfVO!?vh0L0Cz<2y%+&xp}kd)^#rYC zen)&MYH7cgtmaSpI$J7No4!QlpAqvi^5XQl6tr;zY*IY|(`2mZr0>WbFc<{oKtV7z zZp;nHIL^%U*{UeR*Pe4@%6sK`1a85;Lq8ylHoZtWtF9rI>6mII0=UvWEtj!$t>YaH zFJIE2Bdj}3+g;-=ixvnK$QG8;QPL$j2LhKa=huIp4-ORRl}&PzpP3rER$eZRBPfgd zO3mSAS3~`PkXawK3|*{6qx{P#p9@bg7lcy zrIYK0l^1hbA$C;_LCSCh7+p3z28*n8hse6x_|G55FwitAH6bD4xOzWlB9GrQB}%qR z&m^BGC8Z(9Y3wzxZSx7)cc>nPVEvT?2xnYB5_-xUuZ?b0n${D}EH=#zzWMh~Q+|9f z!4|AGDx;yH2@F1^-2G}-wYgW?ypw!!YQIF$x7L(33W|zBZRr|O003}|SfDa~>iMJ= z=Qg+)X()RHmm8jo{2ClQ&Z)Pi>&m$c7f$~0)=~D@>~G5JOR5K?+c@maNeRa2dgb>A z4r!HhdMvvVzs8w0_yEI&1Bi(_fZO9^X)4rpI{R2TgiBWmUs*4usVf=!$aQOoz#B%p zg7BIOAU$-iE}>A=IPeD>T1i?!{fv0*u!+HG=f$43A5U5jYg+5L8D&)M8cxs2T1BU- zzRFfdho5`+M8_W4i#-Q0!`D<>7H1Z+MHOhNTVY28T217P*FM*;>;OE19A7z13u(mRX4ER5)WO($6&}ROTm)kp%8UM?`3k%}3A)Aun?J zVTyVxYhf+p8gFJk$*U&uZS&Iaj_&<%DnjX5-y) z#8T75|2dE){=`d>(4k)eLlznIH9!w_pFgu9Pa{%fSX_hxnWN{T7GIc|9@Z#rj=t-t zm0-~d+r;ZioH`W~@WxjFgJG5W?ET`+-%)CfetgJ>RQ}M5At!0PBCCevVlreH?6o_-#Sx%w{?HY%tUY}H;Q|N=qI^2D ziH1gUpiouf*SmU=EdpM0!eU2^aUR`-xP$NnNY4Yf4)O8oF_cGoh;o_8b>52q^I~;Z zy>irNe>G&Yb&-r1d9;C&9e#82p)2bsa3IuOS+%AW;`mr085qu_UN_ADZeyox)E&OT zR(BWg7?=6Y(9kxBpEB+c2fx{gUUp?=Wu=D?EO~Oc!5$UPaOn4T(!;|cnHHZzPO41T zhY$R*%OGb3O1I3inC!d*=`BVa)_h)0Lu+I+$o&S?-LTH<(WPd>qymKD=0L~TuOO@AR*rmrQ=Gio>Yt#t4ntok*KGgSPh?1vYc=tJ4oUpz%hUl3#|;YdoVAMoU8_lLp#5FSBo3XI zZ|R3jTje$@%q#^4#HFbT#tr~90E<){xt6>AJtKzbjRt|@)vfKnI0KH2gy5h*vZg(= zZmoLR1Z3@K+7fph^-6Onk1@zFSeNDP`V1*!iLY)SKYgNq))(&|^G`b$3{$Ci*rjin6BAwX?(pABmbJzdr2*nQJ z;O#6c6cARqo8y=$nm)phq-VV0(%X5Z?y4MMDg@ra$b@hpk$oUS577LW!Riuh`tTvc zS1cf8JxUsbsD}Fb(R5MMR<^?+5E`L5>AGq6?%fYC9k7;u4vPOxKQ)(ev~C%;Or4(; zZyHkkxML>=S~B4a9CQ^+yhM6c4asx^8~(k0}7-Z!?fPC+5GAs)e^ndTQ-}VET6S!GAg>67Wf_wtb ze}5~QP}i_VH*1t+l$bQ6?~TW&fId$4AuaBTJ$v`^96h}M{D)-MZm%-Dw8uitgE%_+YC!D9eIbb{;nwk2v_2M8M^NI2N3rrXbO^JIf6HN_%_maO>2ZAQMT zay!x3_g#?&>B ztEWns<^GMZBkmo zZ0fgk;i^l@l?H@zLxX;_nn-+0kO;+p?%d=<9}z$od_yk-N^f~>ZSCwQc-gke_xXn( zYrjM@tzF`3-$vVb70rQDS|85E@0T1I!|vUXa~9GZ*0Huf^3?pv!FYwl7Eav>RM~+G zg_#_}4ob?(0{KdC0UH=|69=xW8%1D@rrRo zna-5TQda{tJ`C@JOq2P_9Pe?|SnyM6j?L&&_006NZWQV#z%VK$_9(XO$o^!{mXVf1s z2Bg#oFfaCZPmQ-wmYFWYN$mmZ2SplBEadmNYoBj4eJ?JHIn7PdCm5lj;qAH@{XMdx=u@Qr9s%8h&=~Jp zx*FTi+PVexmYzuvhGacOMkjXq#g1~9O2++>`_)OjL3e$K0Dipn;Kx!(yw@cOgHs{WPa6k!EoVZ&>WT`CI;*f+AA zWrfS@x~2kB!J`hUIYA+2 z-OSMD&5K$n0HwD;6PA7$Lbdz^`?%~!ct{K^0)FF@%O#GRI$P7_A71AyxBvUtshN%e zk44V5>M;1TKthfhIIBD1NXD?hFn3LZlHD~R=xv9t;{KR^8Dd?Yqyw>N#nAUPh2gqKk8bw6cCAIYEhCe|%&p4z2shn^`@&0Tfrz7@ zl|L&4x-W68ML!{X`ZG=mE(wV!_q-~G0zB`!wzkNnts>V;t{DK#rAH%=j?ajvU)}4( zHeiaWUp1!oygtkJbeSpw?}Dp99ggBU->8{-4#lURy#PQ6SIaliT8n&v^UJd+Rc_|J zcKYRBfw}tGevpu6?68blgw)=9Enh()B0eV~egR;%dPiU(&k%#K@%0lAKZaEjkYJ|q z>)M(yL%`i=Xw8niOjPXVc*}r-6KQ7J(j~yBSaqx6Z&Sti6rdF`*Jz*k1>u0?&h%`& z59oTdP26ExPDciy;#k`Z#*cyV8$fPZ@A)MIa76EfWNcbw(EK&2w++eDi5b^@r zm+29F2wClna|7Y>A5Y(EAoL?hEJi;)7M)sv%AN8UMH%5iwi!yx$?Zgyq+jm+8>tgP z+h4VzeWn!?ZCHln2);Y|*K4cM!Um;~ua%(4_1%#eN=wDN3F8 z$mi>QmF?dmTSh{v8;&|;t8O*}Y<$}%sx^6^!D8P zJQ)FnmK8{ew@hjlWYsS58Dmn^UJK$y5cp*pL`B$4h~a58ew;@qYW7F>tE^lxmB~PQb~gt5^Yq z@VcW?Mv`XfsA*T*CnMG4Mq|EzT|e5%uD< zge89$*q6bZFI-nMqQ8+PR6X&z9|u)K$9E=X8B zr9gtY6P0I{VLmJ5OV{xrh`++^tNKW@lrDlEHXt7>&e^wb1Mc#rss`Urnwf1Y#*7sehVTaA5IGD#pM-dUJl=1& zutjiH`s(m6R`{&e?o5#VU}IpEF7nh9Ojj=K1xYMJ<^(q)n*xHPX#!6uJGWF4MTuDf=ncf#6OX$6>fLQfECK8&(MMU@Yz6-pksmlo0Ms2i%a74< zB3upaYM4QYJnqi7M4pG^Y8^yD3GSx6(OcJKWm$q!B(q3G%O(|Z$#f$X)s~F&DRd`u zgRd9#R-z0XL_b+y=q3m{Te>$l4*)vFQm);25IJDVPCdSn)GhExA_`UDj`6Gp54fTE z(YIwiNr$}Xdv~Q#MsbD+S~7KrEX3b;UoBeO;9*XSc?n z*4E}sw^#Om0vI^fA24h_t!4_8A%HbR6V zd^sXoJp%$8reTd;F*w_}_U(&vjt}7re z&TS4CWb$LRI&am%f4k=UdvQGgB>n9x(}YF~<{KMkCn|M{BOxMePk=$~Hog7?u{22C zv13R7=xCPfUu=Vvv55%@7RV&q`;1I+?84>OTsyrlDq_VqIAyWZCK~V&fnMx58<~(w z!c~db(Y(tNtrR3S=U}vksNd1mJbah}0L0q0^Jx0j;!Gx%eideYe|8_omJxoDmk?@J z0bq?2ucQ&H8tS2^CmfwzTpEBuTn7WIwxJ=wC=rHi0!>K2M6wFOk*`Hlxd_ap*p!lx zNL6k)VUvFe@=z@k+q35xroUm({IlhTAIJhoPaDzf+0&<27An<>Ep6Df3M3?L)+A(S z3rxrW7n=eJFnfiWP}RV3hK)?;jnPO2-%3HUY{{w8nUe&o86;3w5#Rfu;9=~}Jn|gAwsKn{8O-v~%DOD}f zQc`{JF(MOs#6ZW?`O+3^eY-I5K{5;L0Q$H@m<25_TF}9uGLXa^VL}A}=p=m8oOo@8 zk0BY~Dd$CO+i1PLfuWLdw4^BjbY1$d4OrqMxR8@^GXK(B@txX$`=LCc!Lo;&;MUk( z<*5<3ZiN8a15BfkqD?IL(SKz3yO9;LWuzYfIqB$9!M3s+uwtC@{jy88P5(4El!g2UI9#6%YK z+~D`L1`QsVBq|MduAn+*@6tGSOa)eeU^;DrUm(~)UJ3DJqN>wAx7u^yD&518fPvOL@IikJq#G9Bi;D%)-zuMzBsrvM=-PUE}J?pEMk# z#(+E`LhXTqL}kQv=_jV za(RD0e68y^iv@t{GC_^wS~IX{ScV&^pRu%D4c!-tjL|%EvPN1(2_+5$vhtX`Q_rr$XyTpmJ*;m< za|>!FGH$_03@&**0)(Vgk5RW6MFzQ~v-b@@?Q&J{fVo_&s3JK-Ly!`ml_-oDmjEuj>Fpd}y z_^3B-gt_fZ$}g}!gTrjHX}w#wA#o-MpWK0q2q6$LT|u!yjN1|2(@@N?PCQmfd5TCe z2_6EGC~I>PpwFzb&Jt???-lA2G0z{ z9db%SW(zeGv290raRbfTA0a`%UPT5YaSn=Mw zPN32!9`y)EQ+N$ns!w0P{_)HlX}6N^M&f(|z|?!;33y4UK2MC9iSFKAj~+e=Eiew) zB_y;FO@ zzY}6$Ap4tQ(j)}&7UZ&!*V7T**!@Ibi{Hf&-(qlg0;*_KzqO5xDy>Qu1NA{rQt#%$ z*z+~W$~RTrGt~k#f`{=X4{)mmwD%;tCgHzl{F?^5WGLVVEF4nuot%FI878=0G^;c z4A2)G?s$~h_GJORN?E{bIeB`#(Zq_gp8W1AMlOztP|)GgcM?P4PG)6SjWIP7@D5!{ z9)ecE>iUq53pnnH?)hj2655fD%QOe7TsRIA%#_T-IBAWJqBsQf^1;V2w%b$@&e>CfM}OAs>^PP`yw>o`BGP~8)4;G+xO&F6|YQ-Br+15_G3Xq0Um0u)kQ&A~0Wf>u-R%HjTBzmg95 z%E5Cf7<)gb`;pw7JQa1oYAip05HrzUXkPxU49pp%5WIIx!29x`AGfDmL#P*toj1sRxue5LjAMRPyW zt`fwpuB+qPFZr*-Toa76%W!?^PYwv6YJ-^WKKyG(BHVll?;YD9*e-g?@fm~2_1FA3 zG|=-T1FmZS&RGXM5R~K(iODwjl2q|kTL*=Pf=YcWnKniSd@iCf`Esz0OJ}8s*wC8L zM*w`a0%D%p9LBO|9d5!-$a|@EJqKVKOAx*nkPIt#7J>EaJls?Hzbo0zg9*l+wh+Z1&hSS~jRZD!>JIzbyz@Pn;RC1@0dNCl~MU_}^6u!WRQ=!*+dv3w36t2H2lb&h8P&E^y{zIpmuN(H{S|~UFXdL?gAs#w10%~-wdWjsw-m! zpael8eRLXBbu2dq{V#yUYSY!Cl^z>w0+NHBBB6?xej9)#6cgU_QQ_Xtw#EKrJvCM1|}Px6%c6& zWs`uN!dePqih}1S4s}G^IG9#jwVp1Zjdcao;y^=-NgvK17~h3$-R3fxk{WtZQgX3} zo0pf9o12o*hrXT=U$c&8S2HA=yUPj)t`l2__Kj`BMXVHRJfTiiGu$A6^i(EEK^&KmU@5ha zk1w*LmM-811&7mzjcC+CAc-A^w;C6k4(O=>%4{U*AF9HB5Xo=XSOYav21{T6{XJ|7 zm}ZVSBKHCqvK|l@fmR6#w z9X@}5H$J5vwRbl6A$)TZj32W^3`lkbL@50vA5J}hCWILTGRji~5>nYC_vG5W`=-f- zfAje4@##D3rDS9*0p*i%f>=^vI3kDv0+>0ef@**jpi$y|t)Wh;TBL9eule94vjN!* zQqWW!3Fujn$?EPxe}<>bPU&ki(5wr+trIMc_^C#n55~hi(t*3!?Ayh|@*nHCg}<5d_>jdcjw?Er;do3Q3g2pM#8ZX+%2 z$nG@S!B6Oy#WcI(3q~KKNJpW)hVV~s5BA{gFd&sni+kd=C~@|aLg6nYC4u9R*i8Md z27#}BejIu07qc5%z^XV_EeAbb|L$dcRs$Hyhj)J)7^pLjdvkz{(!6bt7m{T16H!Uf zm#9%eFb5mG0r`2Bb#zn|0}x@I#qF=}9V#qlzZp({M)yUlG*C0_&CDNkEUc`YQc?-5 zle^^PPG)SHC;a05Y&h!9`Q(ceHK)w%S1^F zy;fkOTIIS1$iqTx?sCVfZ{5B<1aVfUg_VWHF^LwsBC3}?m&1{{wlj$UG!+eh*SfOC4lGxsHyc2={cF=2G0nY)G00X&L2^iY)M({?ku zcyT$RHJ_@6>5W#|a(djrOEvU&B74m-oQx?Z4|c%^u_eT9M@f-dcf<%cm;`}sS5WE% zq2Dl>^X$V1W~ePVchYZfa|W@7IAM9*kwxKsTF9wc+@k*K*p-bjHOkn^Ysr~t|tr~9_Qgu%qsinlNKzqxny>MCNp!BZ|n z-3d`JC(-p(GVJQNk3&_L43Og#Jem5m;pP&wWE;xi9`Qt z0K58e$S-y{?k@+csA}boKiX9{F>*}M+sJ>Nt`m;m<_0C3-9MGTXy9@JeIlI7Z|`y1 zWv_V`lO&U?l0Zak-=V+m+D4!#&PQPTul}&CxHT%!Nx(FHg}0}t$EH0iGWpSz&)T~s zZ*zidOsvJl#I6(-Ic6WiLa3dKC`kN3r9~VNJ8r;5Fo>VYY9Irv4I$*p!U0UT4N|X{ zIsZWnJh_;qkdP3rbq6`d)iLCTDycQbe_z`{C@GUKlVJ@Apwn`_-Q9QinVf#1RLS9{ zU;a$wW>BF!l%va?I(>X!nZJyF`&9mJoay!5chLb0R+j_=}1kUbmS5+vt$r~@DGh6YQdS0XYXwtE|C<+6OlTzxV)SO z12Mm@bysje6`6@#w;*eV#Y>AyT^A$h2Pm&Ua=L@E7S$M!v=QZg1Ltq=wFptaMfgpa zHsWqtQN==%Z(Ig`OFeB&LJ0*jRS^`Q>;v4vak~u|k=LqV6Knj6TzP;jBk0UtJY7FN z$p-W#Z)e$LN%RVYw5n}VOC(mM$-QJcoxHsh83`CDNcO7bKo=+)w)LVp5}_9nc>AS!l8u!LM9Djz^${E=VauG*<3#*HtKKnM}hI_-~dpP$t!> z>I)I18KKXv)>!6Re{MWZ_JMpe*Z-Xvg7PNiX z96&XUt>|eW9(t`koij>U(z_2Htgf4rXDnHgas8gv=Gv^_`McddKAoR>@3VIQX}wBU z`_gY)G*Ff($7h)N)-1M{WoZs>7wGdt8Be2~O8vGVM`kfz6#&dGxP+z>kRmYJrXL3@a0(*E6OtEV z8vAn1(aA|S$}!Pz>>Rtk{PacaBZl*GLJVF0AK*lrf0=ZVP)O+(11;Q`qj$H?g)}TnF_4B@x#Ysf*s^fl>9;WCrR`bS zrSP)Ce$9_*CWdoh?hjqGy?F5$jZ?bt|B-LMjkO|aeb(~HZ%&PObi}jETEb}7(KgR; zr3%o86tl^BF%z$3ARaE6=liSmDrH&?B?6>OzA55)>z0*GSK3g&eJB#Csc zd6ZpyznZxHer5X3LeB7u)+uJRT7S0Ta4uav(Mq`K5D4coP?49FAc-dY9UZAY^d{!_;HJM?0FPYBfKlWn`fkZuj z`erySVus*eNm(T7M&GQtv!Wo+2)(Q+7;6bAk(f!No*MCoJD&*`b$kUof7Fg@z|l8D z7ikr7HX20Sl{Sglt)-xGer09xQYr7eq+UaKt?x$GKsy!BGaY+v71px%HY)_5{t7G! z4Un(thb)n8>gi^pVur(8DeWpxXvx)S667V++9g3#?~?>-X|shGLt%GjOD3KJLPji< z15VoQeEZ=ApZH#;F_C&PIXSct(sUJX+2qK6u)75~Z4f<(dMi}s#9s!Gpao`nI?XH4 zFZxjQ0V%eNJ>>hlDoWEv!uapLiSEO}VW&-@C9hY8V)nQRQ_>_vDOjY7f@-1XQ-yvti)_&S-ME{&Ata85dip+0Y-@(Ca;aXIGXUfYAvN_?5rr(q5?ntsSjOcEGL}i++EAjKeUub*DwLrsBhAPmLqE^m@6rEg)2Ym78v5c%pkzU@F=dF zaiVh28pA9!r4Az;0bRs<4`|0x2-rO1 zZ@=zAS6oS1ii3mL!fY|^#UL1+Q>(9w&92`jd5t3hbgx<=%L+1QL1_{F7}8OZPkBz< zWPrEwWIP0@1^btX3bS7nTK(#nK!+_naT%K8+%`5LFuKSo32)*0%9miy1Z8fALmg&i zcqmMXDj==w{IW(g`-e=hR$i2Ki{vLunbEt21drI%~vNdwSu7l zR4g)_%8w}wKz&|oB^SC`q2k!0AFqu~9OJhW^21vTzc<0pdEC^)i(~Ej%TPup z%iKNW8-`g|n||hajdtWR!InbYY8|*RM8$Y=4ipAmrU_;mkq?`hu2I|Ty1>cVX(B}C zUple+G_C}w0hPL6xkJAK(WC+3&G-$l?|z%yJKpDb??YydP{B%Z?Fi;U7lUw$Tz(+Q z5QjVbfOt|>>g?4Jlr@J1V+EVA4ue2boJ<-b4B7g?tx5rjXVwWssuCv`o^rGX@MJys z;)#>^Ub`+hLO14yHDh8JCy}F{TucQ*M`mOXh!e|#nWk7JJgy|#hzM0^mrJifkREja z^U2^@U3JL?#!?*cEyrKfp9}-p=~&Bi!3Iq9>y3j;}xMi)X$g14aks|M`mZ0oZx6Xb6n$K)+y z?hKxp{-Kxn#jnr32itXatRtk$)%q-AGc!De_GWGB5p^cSY;RLAyC*(hR%pF36}rvq z%;z%4s#rFZY7n#Cc1K900o*TmZ7*>QLqVJ-csZ#=DH$d;O)dK6sEvsN_WiQnOM*xd z-7N`EEI~7XXCP55d{E0t;002-6`?f<05@XK;TG7S5khZ{@7`-E)PQM{EiYWy3^8b` zfFZcLnVN~KVJn268$be#K|=NDVK~1vG9Zi`cvj*-iFjLt1&CY&%f}MELr_=_BP0|4 z4NR`cKt5PLwO638ou@<3QT14w9j&Rly1JM!5;n;1u(9DsfklnZE66uoKvwZS>jCjJ zH`E-!J^xF5`**}61(XkjeS`H{Urt?tK-F{R$WqUGR)8q=L_iVTi9@or^qdiCaRS2x zvDTERYVozhA-aeCo_HZ4J12{(II`2k<6b%~UrVTPX>U3H~MMX3-h+)bC zta!6KH8~@-`F69DQEq`jL04f448ar`u!d}!O!@@TeQ0(=;z7?xh6-cfo-KTV3?>Gi zwaGyCAn8;A*rEe$6FDs)mPDJbPGDEymSgmi?$_fO=uopVtpQm$;mi{wL##zIUKCjz zyt~6Msn{buV{P&UIosD4Yx&Y$#R~JT`rh7qHggU$U$jU<3B%zlU@U+^#sR|{MN zfyb^dlzOkG4~hotB)SO=KocM@w4pZz`Vlok#Su8dM#RNk2h3#n;>A6hUxd|jlprEI zY^)`W1uor3=pD5hZ!?y70B>fxKJL|+Lx4&z;DC(4qRE{p)UA_SU-_I zG!QWQa2R~3IcNtUnLF3etJQ0Z)*O=U`wVU8v7T-Rs~aQymYk`hKFHil@ zZO=8dBMo-upRxo~j8cob9676z>_9)Rs~~PM15F1ipv;P}h)0#tQ2@612I$*vpruhb zLk^2OE7bCwkpM)0r)^woeiMy;pXKrWc2TF~Xqd$)aE7;EcAzv1zX)uD*toY}sD$1a z$mRPNp4a9p>ySmf8w+za246+$6%!0Ev@xsF>Dw`J5MQ}qVbGBSL|${yaK~@}xYnSo zs?Qb;hrmSg`CYB&jcd>x6HJ7`M}}?%8BGOn5&jNAf}-%bKoG@bW94r`zp=xVG&OH# z)qvu-75jkoSJq=JA<$y{TGY7d2u+!bR}#52hkm{ZBkBAj@lfL152Tt6ymWu~B2x?Q zN6Z0Kh1TK^v`WC9N1hodXl_j0@1ZIRT(v`A98OA@tPsQMq5p2w$qRfRR(xVgq}T}H zq2n-QL90ml$$pUAN&h1A*uBkqdU~bH+ze617sb;ln}$JF>0iI+deH<6nb-?#6`@#D<6JZ`(#TXB64@l;cn6a@){ChtOQR6r3S z*4OTTc+rf4@r5aL?NE~Vb?MzeS8xEBK9ospg+btotR=k^EcErDtZ<$bEuv?x>Yb}{ z`L=Cuu$dZ`#Ux2KfaMdYVszhJ5v>CFfKQ183|ko1+%rjKOe$w5c)z7GvUiGu-pH_+aJ$g=@C9DX-o`NI&af|?@0 z|0Z0HBf5zYf{^!cf&htzCy~);Gu+J$Y@iz8vH9PF)9>H^mt;lUx5&JqHEY&Dq3{&+A*=zx$4^%;9l?wN z^i#~$kwsizvwm|-6eC2mST>lqC4&4LO(*0(SBV1d`SW$qR_6y*WVQ`207eQzU`1D} zAEB4njX{gI0kDovP+V7m0fQcgG6HYk_wRZ8ds7gW2wg(mM-DWZ zI&4Je_l3o1^$^Au`*XlJd&nk5^I&<>Y-{wca!%q84(~z|%K)K^?cGb7Xn3;gfvPY^D_)lCkn*Z znSH+46zKRQl%HBpO-7_Y!r9s9PwTPQT_fMC-z?s~)IPK5KC5!C`p`M=*Innvt52Tu zen0Z??c)?}WD`M{=vj93hX3!QIL|jG9k|tOXlxz(;mj?2%Ukx3vL`Z!GUT50?)zYI z4gcL_XYYZ|z)k3Y-giywUY@L7zh}LHsOUtF# z(PfQ0J=;AzuPpI7tfFZ5Ln}?}XOHK^OSN0!(q9tT8y?mj9^YfLj^gmvqq5^A?jv{H zQt{rzL0w7MXK(W5Fw54hDmy(+-+4@H^Y@cD!3GzHcEV_U8G^(9AKUWkodqZ==~okZ#f;UP@=U*9YQK)d zNacyoFH4jZZAkK#E^Kb4kj%-#eqm z&v+s9myGi&e^ku9=Vg8LMs9g`b8~YlC|=0b-l5T(7_nKO6L(F)Js?3tL2;OKHPiq2 zPode7wEu7c{^vjSfAc$Y+uhbs_|Tk0`s5BfF00ZG!w}&|A2Gx`_UTB-X)7-4o zw>nD!?NK(Lg?`6QGdE!)pV!hO^*Sm$3@(nZUJxhWlcfWmwzBvB)yXLpUo&qFBv^$t zd5sq}?hJXg?^E9EPn45Z!?Lllk!Q;&awb6&|Kc^Cc=m4gyGIl&9~O^VTc>Mq9A-2$ z=9%vt!rbcIJZK!>CWxI?=ch&u(drV@kLJIp`+(SL@Iv>j9|KR1?6XMUmnXY!c+fJT7W@#f+xM_IuiDrJ-Rag zT$nt=q-xlBNFzWGejd^2s$bTK&_$JM7v)fn0aYEksCg) zM-L5+>w#ah@5mv!fG7I*BTCG{1V z;h?80dFtvK8dl?&k@R!>djNhWi(vr@EZgo19$XxXF2amAELk}4y8kF2;p7~EMmYqp zau2ZISmQsK25@uy(WioD7qUh+?f<+5Mq@VtmI(HqKsgKBc=PX2Ma9+Ff;WnkS(F!(Ef>Ial6h*}RSF)w zgV(oT$Z|Dazti1<&9Q!Ksp~XoXtQN_AcS(*QS%tsujA8I>+7MWp~pgf?YRTFdecgZ zCnjQB;=l>Vf{`geio<;HvNP_wu-STUw2doFW-N>OfV)G_x56MZ*N;4zNAFaaeiXQW zo6dK!#XL@^aFF7_a8tGS>$Kk7vjXRhSa)*i!m-b)Z zZn(6m8vRY;I`8o~MQ%PfZ=!ud-%X?#L%hncXa+NHUC8-)z_M|N+s(l+S&_>>zbCqR zrerz`q}$+742F31(Af_;M-d-ZdjI?~N26BwHOPZ@Pq?`_5id!10@Zjgv$&;F47_nm ze*^}CB=UUUr}Olco0-0TMCa2~!ycL+xy@_&@dUc1(Wwe^+qBqL@ z`I<|m4VEY3%o~G)d@xbBJ=UZ8vT9C?1~z7VqF~A&`rEJF*v9>WlIg6bM^ZxT7dwYu z?V>y4DqFYQbFW!-$W2UK%gt>IZakj((iXg;?}fB6 zFCqMtKF2d&Y;=D%U+KfJZj?O`1?KPc?dZIHexOOclFx6W4-ZE2aQ>mgp%*i3r0fts zYfA!WXm?sS&(JZG_i9|j1rE{qL-D^)jMCkZgGKqllFrB^?jNr1DaU%*3~5|kqNo6p zBOIXqoLIlVbOpVNDBnsd5e;FBcf9{Iw>zUUIAz8kv%Tqldzs-~{;i&Zva|CPR>I9= z|I`x>RmGC2o)hGyl@NrT$azZt25OLVpwkkqA>Fy{;r+b8DbD+Rkdz*B?B{F#+3nxz z@}CORVh~!=CF3to3@o-VIE;RYdGe=BLlq5*8=9jckTUc!xWc9`eIO22x-#oCJyN*3 z%&xYQj3stp)r@S8yEtKf|MI};!Gw0ojygXY8HUR1Zu7#8dFKMJ#RH}_x)t;UH^pOo zK#2dyTP10AYu3ruzOC1%QdUyVbj@kq##`uVa0;=dT*{nv^`6z|U9)8*CUKKs8y>Vu z{xlM^CE*!qjI`PnAerUY5Qxh+7J2DMlx21hkiZdH#sUi zElx8MJUqGR(@MSos3D~IQU5M_Z@Pz$6X_*#0qsRQDBr9}ahZE}dWH1Vb{5c@QQZ4& zhm~Sa{(|WmT(hWQz6wvWPIXK;Gwn^!Kx=|!<132XAx_N8XYdFo7sGP8MLwhb>S%DJMkuyD6b{Id|k8o`WiBhF#6md4C&U?NF-xDCju>dCIUBcbWNbQ%Xu{(O-I@{0xqy4G=xu6 z`u7q8Igme9tZKqQ<}7z3IV7ff?jVM#UtSl!V{Heckw{S(<`wWGUx*Jn*a33zJcYxJ zi15^MmFWoS6yx_38nN&ktC=NJKW_F!3&~=g&fAU0uI;l;%W4SL_V`E;rya1e%J(k} z5_$v;#xxcARf1QtZP6-?_1VIJN&A@@fJu!6rZ$%IE;QmuKI~6*@0)91h2Qa`;!iHa@K|&)6G>6IM2HN(6qBegr)4)ezxDVF)qfZTOMo+WylFguAi;;&%9k6& zCclA!yo)jzz4&7LX5gj<%if*I+57kD;pn?#KXmx{!-t!Wd{ePo)>RT!b_51^?7)^= z5wUaRNT|Y2oy=-X3T{t+7@smSc@m%|INQMC4m|kH_d861wleb@c%j7L1|s0ryH#dZ>u}o8nEWy?RA7B3&GwSK z*K=kdLM|iw4tzRos9~#_GnCKZ1 zO8a6C_Xw?1@j&xlPoNw<;93MoQ4e^epbqqnGPw#Cf@+F}f8 zC`be~AMI&_^X4o-qtJ};FeSxvO$JCIu|0}NhLO#FG(~WG%gJ5U6Lbk^q`0~Xn4SmK zT8gG2boPxGH#O>OLB@JgRl)&t#cmH zv$n%M=+V4=H-B6MUX>+!RRi5DbpDEs$HH+Hm&NLiSPzp(F|V4gnk+{}xXNJs#eR12 zLoALvn1GLRo?VEvNo;v+^q1Ye@OCbb(4TPX1}De}h{Cf80V34(YH_bH=0!$DH8?H( zFRCPS?XLIx0|u_qtR6X^_h+Mdg3;gSs+_+V@NdyyoRBwK0@ex~qGa|>m@%3=x~HjS zte~OkNF*-(I2M(FW7L16k}ij zw6H5xqlIPT&PkJuJ9Mz&9_F$a#GpDXMWuOdP}BeY*?G&em+Jj(v zgX*sF>%r$Tex)%s^PO7o03wt=ag+RrXzn>0652vH;s10{!j{tfAH1hEjTni3U)%N(H7fFBoeIEd}4MVjq2`zm1fEb z8urG80Ll`o9Jo;y>yN!J6biv9RXNrJ!wAtbbH*2yxIZl3~W|wOMnmDnLn``$B3r z=QYe?=%n2Hba-{5qMN`kzhdSUa&j!Q_~bv&ss`}>+pPg|j75YbPocDXKimftSML>A z_4?Ha$F>__UqUcmrXFx0xf?f2bM8z3yzOjAqU3~V8mQ7Sd}%iBUXy9PnA#hfw~>R> zJ5MDN(=D{oe`Z~mFF0ZsR+WwUPwDRqHVZKfV<`f-GZ3;`B)<<)2SUsr*xBFLA#TS0 zb6%}ll3m{rzzDT14$P>w!Hzm_;k@kFirh`c zj3t8(FS=BOC|{N51tpmv6)Q)IR&Vn|@{Z&O4Biq-7NL$p!L62A_^MztPNG`=qLPVv zoWd$X~D& zN=yjwlA53xcdt}^Z}#RS~--f8etv&Ixt5<(25A{eH>#^#C=Jj>gpBTkG_cT&Bmw6Hc ziRmBq(RRWoel}ao%Zt?oR>icEefD)j#_m{Gb~arZREZ~Dm6&aC{+D?#kJRpeXZ9U7 z8Q&Z{c3aT8@Fe!^y0MYOm-t+T$2D3A62lIni0aUV3eBu{++S3!3wPjlXh~#yd*XeD zTVtj8AU`EB`s6as1h4!~iZWApRcmKdfX~@9Q-g*ne&0L;XV=0}` znmS97#=&=u|5KNy8h>DC{^pvATOOV{sjhj?v<$5Av6M@FJK|(+Sa~sj_5^7!gIf{5 zl1|7j1wM#B*B+}X;sAvs=Iub7Fqld*36{+VQCF%~(7hb_$)9-KU;j2c_GPz`luXS4 z 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 MObofd{C@6#0UXKang9R* literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/d03/reset-complete-ko.png b/plans/audit-2026-09-23/artifacts/d03/reset-complete-ko.png new file mode 100644 index 0000000000000000000000000000000000000000..46352d113a07cf8072e1ea590212406c3e184bc8 GIT binary patch literal 63591 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 E9~zbM?EnA( literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/d03/reset-pending-en.png b/plans/audit-2026-09-23/artifacts/d03/reset-pending-en.png new file mode 100644 index 0000000000000000000000000000000000000000..f3ca505f86c1f2e78f7140f9ac3925cc43920f9a GIT binary patch literal 74019 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 Ol(f`S$wG01*Z&PuM-}$~ literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/d03/reset-pending-ko.png b/plans/audit-2026-09-23/artifacts/d03/reset-pending-ko.png new file mode 100644 index 0000000000000000000000000000000000000000..b8c01fe7d100b90e00706738eb4cad5b324be9c8 GIT binary patch literal 59220 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%0A4_jE;0)vqYs2AsQ28^ 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 F^*?+r|6%|D literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/d03/swift-typecheck.log b/plans/audit-2026-09-23/artifacts/d03/swift-typecheck.log new file mode 100644 index 00000000..e69de29b diff --git a/plans/audit-2026-09-23/artifacts/d03/ui-capture.log b/plans/audit-2026-09-23/artifacts/d03/ui-capture.log new file mode 100644 index 00000000..cb290f19 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/ui-capture.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/capture_reset_recovery.dart +00:00 +0: capture localized reset receipts with bundled fonts +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/d03-final-validation.json b/plans/audit-2026-09-23/d03-final-validation.json new file mode 100644 index 00000000..cf283943 --- /dev/null +++ b/plans/audit-2026-09-23/d03-final-validation.json @@ -0,0 +1,65 @@ +{ + "source_freeze": "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-source-freeze.json", + "source_files": 579, + "full_tests": 872, + "focused_tests": 71, + "coverage_percent": 87.99, + "coverage_hit": 12253, + "coverage_found": 13926, + "post_full_test_change": { + "path": "tool/qa/reset_journal_crash_probe.dart", + "reason": "QA-only fake native observation now explicitly unknown for syntax corruption cases; no product or test/ suite source changed", + "sha256": "5374f12a5f0226a6d65b2359597da91002d50776f6d3ae6e1b736a55bc87405d" + }, + "host_sigkill_cases": 11, + "host_limits": "host process death; fixture providers and deletion adapters", + "device_verified": false, + "ci_verified": false, + "artifacts": { + "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-local-only.log": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-p2-targeted-final.log": "944374ab1c830cfd853ba2b5c780b8d3ff056b1cfa88415dcef15fca151c3c93", + "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage-policy.log": "71ba8864988773d2a31e36792c98bf8d15cd1f8fef1a0df00a68d63f8f218724", + "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-changed-source-freeze.json": "a4ec90fc931fc9cf61bf0c7869ba4cdae045a9d32e7c90f62aed0f079691eca6", + "plans/audit-2026-09-23/artifacts/d03/final/d03-python-policy.log": "4fdc1df0b7744216895078fbfd0411918951ced6f104fd7c2d803096adf589a8", + "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-generated-policy.log": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-source-freeze.json": "8e7f94b6452c9ed3d34b532d09fac1951926afafc02d4153e56151dec590a53f", + "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-coverage.log": "36a879b0287d0e34827c8c3006c4139d35719add2b1e38be811928b68e292828", + "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-analyze.log": "0105662d4fa2d1212accc42aa4de1a520293c00b29de7852fa8ac434746af66b", + "plans/audit-2026-09-23/artifacts/d03/final/d03-manifest-policy.log": "60ac45f9be8ec739ed709fa43c33c8f51a94665b6bdea633dd65e27529d13c01", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/result.json": "0fdd4b1d636aeee9117f65b25724e7874ea928369f2cc641a0a385cfd529973e", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/intent-renamed-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/partial-first-pending-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryMarked-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryPublished-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/completion-renamed-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/recoveryVerified-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/first-ownership-written-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/database-deleted-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/marker-removed-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/quarantinedPending-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/journal-unlinked-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/final/root-final-analyze.log": "6a264992dedc95ff2cbaed6acfff786deeb34e1ca3746c93a8446d729f900355" + }, + "implementation_commit": "5f3adf11aaaa3e36d6e4e25c41be6c06e4105480", + "normalized_logs": { + "plans/audit-2026-09-23/artifacts/d03/final/ontime-d03-final-analyze.log": { + "raw_sha256": "8386c45b857f26046fae9ec03f3a4c747ca99b606b83c503c2f38509dc2a99d7", + "change": "trailing spaces only" + }, + "plans/audit-2026-09-23/artifacts/d03/final/root-final-analyze.log": { + "raw_sha256": "e0d6617d592c7b0e5a5e207e2d26af3970e2b4f1978336a2c72e3bfcc81dafb1", + "change": "trailing spaces only" + } + } +} diff --git a/plans/audit-2026-09-23/d03-journal-progress.json b/plans/audit-2026-09-23/d03-journal-progress.json new file mode 100644 index 00000000..35703bdc --- /dev/null +++ b/plans/audit-2026-09-23/d03-journal-progress.json @@ -0,0 +1,106 @@ +{ + "audit_id": "D03", + "status": "work_in_progress_not_product_complete", + "base_head": "0efdd3adb28293d3ff9f6b80bcd9091229384bd5", + "root_continued_after": "Root continued the original four-question D03 agreement after agent usage limit; original agent later resumed independent review and corrupt-journal remediation.", + "limitations": [ + "Current source is not yet frozen; intermediate full829 does not certify later changes.", + "Independent review P1 legacy marker-only false completion fixed locally; final regression pending.", + "Independent review P2 corrupt/partial journal recovery under implementation by original D03 agent.", + "Latest analyze had one unused import and two harness brace infos; root fixed them, clean rerun pending.", + "Actual mobile delivery, reset process-kill, backup exclusion/transfer, power-loss and device read-back remain unverified.", + "No D03 implementation commit or remote CI yet." + ], + "initial_milestone": { + "checks": [ + { + "command": "flutter test test/core/services/alarm_ownership_journal_test.dart test/core/database/local_reset_protocol_test.dart --reporter expanded", + "passed": 23, + "exit_code": 0, + "log": "/tmp/d03-journal-tests.log" + }, + { + "command": "flutter analyze", + "exit_code": 0, + "result": "No issues found", + "log": "/tmp/d03-wip-analyze.log" + } + ], + "source_sha256": { + "lib/core/services/alarm_journal_store.dart": "eeb1dfc3ccc31410904300f1c354dc79ae20a7995122e34eab19b4bd98e96f93", + "lib/core/services/alarm_journal_store_native.dart": "b480ab4756cc2c288ab1e462d6c4399d797e01e38455c7ab78b1c1d562e632d6", + "lib/core/services/alarm_journal_store_web.dart": "d2bf8aef8f0b81c40e400c080751e64c8991b0db7dc4b7bf7f52f702a129da75", + "lib/core/services/alarm_ownership_journal.dart": "520b650543549b445f4a39ff1f573faa457f85b0c68118e4459d1e2a5e85bb9a", + "lib/core/services/alarm_operation_coordinator.dart": "6a3ea664397adb76725f33be9fc7f205575da8985b24e522d3ff419e741ea536", + "lib/core/services/alarm_registration_cleanup.dart": "5383cf1546ceac95a3717238eb15830dd10d9646ba92651f5baf1ac1c6e5bd77", + "lib/core/database/local_reset_protocol.dart": "ef2b4bfd1476f751368fbe7d7ad96eb6906b53a9f04e30530a98ed31fb7f271e", + "lib/domain/use-cases/cancel_all_alarms_use_case.dart": "bdbdc24b3555a6ff5cb39fa145c8c18a0996f6450bf2d5d76294774a61652d2f", + "test/core/services/alarm_ownership_journal_test.dart": "1fc792c51ecb4e7d46864c7f48f21b239bea43e377d54d4c88e4b7082d485611", + "test/core/database/local_reset_protocol_test.dart": "be20bbcb4975557f5c15c0f831d076caedca28a3e62c8221d1222d92220bdfc8", + "test/helpers/noop_alarm_cleanup.dart": "3365248d587283bb83d7f5831b614e39bf6417e751127ae3c871810752eeb184" + }, + "logs_sha256": { + "/tmp/d03-journal-tests.log": "047e82ca08cf2a93d6bdc7f0e6d76eaadf23651a9ad4987a50d73a5114face95", + "/tmp/d03-wip-analyze.log": "d61f9381961b251ab8a15328726ba2680a775c707c7e4060c465b74c5fe0b971" + } + }, + "milestones": [ + { + "name": "product wiring regression", + "passed": 829, + "log": "plans/audit-2026-09-23/artifacts/d03/intermediate-full-829.log", + "applies_to": "intermediate tree before later ownership, receipt-cache and corrupt-journal review fixes" + }, + { + "name": "additional reset boundary tests", + "passed": 45, + "log": "plans/audit-2026-09-23/artifacts/d03/intermediate-boundary-45.log", + "applies_to": "intermediate tree before legacy marker-only and completed-service cache fixes" + }, + { + "name": "host process SIGKILL recovery", + "cases": 6, + "receipt": "plans/audit-2026-09-23/artifacts/d03/host-process-kill/result.json", + "limitations": "completed-write checkpoints; fixture providers and deletion adapters; not partial-write, mobile OS or power-loss evidence" + }, + { + "name": "widget raster review", + "count": 4, + "locales": [ + "ko", + "en" + ], + "text_scale": 2, + "logical_size": [ + 430, + 932 + ], + "result": "root opened all four images; text and retry/complete distinction visible without clipping", + "limitations": "bundled fonts, actual widget, synthetic receipt; not mobile OS screenshots" + } + ], + "artifact_sha256": { + "plans/audit-2026-09-23/artifacts/d03/reset-pending-ko.png": "74ec2442a692aeef8f946390ad9df628e1322ee5f6afb240620a1bbb42b5eede", + "plans/audit-2026-09-23/artifacts/d03/swift-typecheck.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "plans/audit-2026-09-23/artifacts/d03/kotlin-compile.log": "21fe55c87849059cb954e98dae4d78816fe4b1576f69fce0a7c994d6e865987e", + "plans/audit-2026-09-23/artifacts/d03/reset-complete-ko.png": "2ee3654d5101d075f0231e44e956a5cbd68411fc0911aa5fb05c1767cfd05e2a", + "plans/audit-2026-09-23/artifacts/d03/intermediate-full-829.log": "92d4fa7bad4aa7d89a1c8c6b3b9dd55450515055fd880bfcea27aa798950bb85", + "plans/audit-2026-09-23/artifacts/d03/ui-capture.log": "46db3ab2f2b5ba8b29c90c35a92f72e28f64c5db8238f07ab4826459039032cf", + "plans/audit-2026-09-23/artifacts/d03/intermediate-boundary-45.log": "8a95d8ca9e73844f1ce60818c20e53aa9fb9918193014cb488ea892734f30a78", + "plans/audit-2026-09-23/artifacts/d03/reset-pending-en.png": "b290dbdf38186a0362072aad9153e5ad3374e962a5b8c4f487423781fe093fde", + "plans/audit-2026-09-23/artifacts/d03/reset-complete-en.png": "ab4494a8f1e3d4a6d0152eedf1627db1e821b42dde2e05828ea530739ceff07a", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/result.json": "75986bf3fadcb7be30c5e40f8f4638d7d64d1bf6767b658ea95865fab8947740", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/intent-renamed-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/completion-renamed-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/first-ownership-written-cut.log": "4205045ac2bf0e285f31476e20c3ea6f62c54006a6e8a25b536ec20fee4e74b2", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/database-deleted-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/marker-removed-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a", + "plans/audit-2026-09-23/artifacts/d03/host-process-kill/journal-unlinked-recover.log": "93e2c5706ef2a3b70328428a8d70f83d5c70175e6bddfac2ceead7ee7fc9249a" + } +} diff --git a/plans/audit-2026-09-23/issues/D03.md b/plans/audit-2026-09-23/issues/D03.md index 985bdfce..19bc2472 100644 --- a/plans/audit-2026-09-23/issues/D03.md +++ b/plans/audit-2026-09-23/issues/D03.md @@ -219,6 +219,51 @@ restore는 journal을 새 DB의 일부로 교체하지 않는다. 구세대 취 > Q4 및 보충 답변: 그 경계로 확정합니다. 명시적 provider 실패/unknown으로 반환한 경우와 아직 반환하지 않은 OS 호출을 구별합니다. 후자에서 UI timeout이 작업 취소/직렬 소유권 해제를 의미하지 않으며 A15가 늦은 반환을 계속 회수해야 합니다. 새 profile/신세대 등록은 차단하고, 사용자에게 제한된 대기 후 현재 미완료 단계와 재시도/앱 다시 열기 경로를 제공합니다. DB/키 파괴는 안전하게 quiesce된 경계까지 진행하고 무응답을 성공으로 간주하지 않습니다. journal에는 reset 의도 및 단계가 최소 메타데이터로 남아 marker와의 crash gap을 복구할 수 있어야 합니다. 모든 삭제 및 provider 확인→완료 journal 영속/read-back→reset marker 제거/read-back→완료 journal 정리 순서를 따릅니다. marker가 없더라도 명시적인 reset intent가 미완료이면 초기화 복구를 우선하지만, 정상 active ownership이나 일반 예약의 미확정 intent만으로 reset을 추정하거나 DB를 삭제/새 사용 전체를 무조건 차단해서는 안 됩니다. 일반 미확정 intent는 A14 관측/취소 및 A15 조정으로 복구합니다. journal 최종 정리 실패가 다음 실행에서 이미 완료된 데이터 삭제를 잘못 다시 수행하지 않도록 완료 상태도 idempotent하게 다루세요. 말씀한 단계별 새 인스턴스 재구성 테스트는 crash protocol 모델 검증이며 실제 프로세스 사망/OS 전달 테스트와 구분합니다. 이제 전체 실제 4문답과 Q4 보충, 검증 경계 및 D01 최소 recovery 의존성을 포함해 D03.md 작성해 주세요. -## 이번 작업에서 수행한 일 +## 최초 grill 작성 시 수행한 일 읽기 조사, root와의 실제 4문답 및 Q4 보충, 이 상세 계획 파일 작성만 수행했다. 소스 수정, 테스트/기기 실행, GitHub 작성, commit은 수행하지 않았다. 구현은 후속 순차 지시 이후에만 진행한다. + + +## 2026-09-24 중간 구현·리뷰 기록 — 아래 최종 검증 전의 이력 + +현재 공개 PR의 기준 HEAD는 `0efdd3adb28293d3ff9f6b80bcd9091229384bd5`이며, 아래 D03 수정은 이 기록 시점에 로컬 작업트리에서 구현·보완 중이다. 원래 전담의 네 문답 계약에 따라 root가 제품 연결을 진행했고, 사용량 제한으로 중단됐던 같은 전담이 재개해 독립 리뷰했다. 새 문답을 지어내거나 모바일 검증 완료로 처리하지 않는다. + +### 연결한 제품 동작 + +- 실제 LocalDataResetService와 DB/DI 전 LocalDataLifecycle이 같은 owner와 LocalResetProtocol을 사용한다. provider 취소가 반환하지 않으면 owner를 유지하고 10초 후 대기 안내만 표시한다. 반환한 실패는 최소 취소 기록으로 보존하고 사용자가 요청한 원문 삭제는 별도 단계로 진행한다. +- 독립 file journal에는 provider/취소 ID/최소 schedule ID/pending/초기화 의도·단계만 저장한다. raw title, payload, 시간, fingerprint, DB/키를 넣지 않는다. 검증된 임시 파일을 같은 디렉터리에서 교체하며 첫 저장 중 남은 완전한 임시 기록도 검증 후 복구한다. 전원손실 또는 디렉터리 fsync 보장을 주장하지 않는다. +- DB 파일군, preferences, 설치 키, legacy credentials, native launch payload의 삭제 및 read-back을 구분한다. 완료 journal→marker 제거 확인→최종 journal 정리 순서를 유지한다. 한 서비스 인스턴스의 완전 완료 receipt를 캐시해 늦은 UI 연결이 새 삭제를 다시 시작하지 않도록 보완했다. +- pre-DI 시작 화면과 ResetAwareApp을 연결해 초기화된 기존 DB/bloc/timer를 다시 사용하지 않는다. 기존 두 초기화 진입점도 반환만으로 `/resetComplete`에 이동하지 않고 typed 결과를 표시한다. 시작 대기 안내와 초기화 대기 안내를 KO/EN으로 구분한다. +- 손상된 legacy preferences 기록을 scrub하기 전에 내용 없는 소유권 불확실 표시를 저장하고, 전체 preferences 삭제 전에 독립 journal로 옮긴다. 알려진 항목이 비었다는 사실만으로 불명확한 OS 예약까지 없다고 판단하지 않는다. 일반 OFF는 global cancelAll을 사용하지 않는다. 명시 full reset에서 iOS의 앱 소유 AlarmKit 목록 전체 취소/재조회가 성공하면 unknown을 해소할 수 있다. Android plugin cache는 legacy native AlarmManager 부재증거로 쓰지 않는다. + +### 실행한 중간 검증과 범위 + +1. 전체 Flutter 회귀 **829개 통과**. 기존 공유 singleton의 기기 파일 저장소에 접근하던 단위 테스트는 명시적 isolated owner를 주입했다. 실제 file journal·재시작 테스트를 메모리 mock으로 바꾸지 않았다. +2. 추가 reset boundary **45개 통과**: marker/설치 키/credential의 성공 응답 후 데이터가 남은 경우, DB close→파일 삭제 순서, provider별 미확인 결과, privacy cleanup 분기 등을 포함한다. +3. 실제 별도 Flutter 프로세스에 **SIGKILL 후 새 프로세스 복구 6지점 통과**: 첫 ownership write 완료, intent rename, DB 삭제 후 단계 기록 전, 완료 journal rename, marker 제거 후, journal unlink 후. 이는 호스트 파일 I/O+fixture provider/deletion adapter 검사다. 쓰기 중 부분 JSON, 모바일 OS 전달/기기 kill, 전원손실 증거는 아니다. +4. KO/EN partial/complete 실제 위젯 4개를 번들 글꼴, 430×932 logical, 2x raster, 200% text로 캡처해 root가 모두 직접 확인했다. 텍스트와 재시도 동작 안내에 잘림은 없었다. synthetic receipt이며 모바일 OS 화면 증거는 아니다. +5. 실제 iOS26.5 SDK Swift typecheck 및 Android36+Flutter embedding Kotlin compile 통과. release APK·실기기 성공을 대신하지 않는다. Android manifest policy와 Python 정책 테스트 11개도 통과했다. + +중간 source 이후 독립 리뷰 보완이 진행 중이므로 **829/45 통과를 최종 소스의 검증 결과로 확대하지 않는다**. 코드 생성은 실행했고, 마지막 analyzer의 unused import 1개/QA harness brace info 2개는 수정했으며 보완 후 clean rerun이 필요하다. 자료는 repo-local `plans/audit-2026-09-23/d03-journal-progress.json`, `artifacts/d03/`에 보관하며 아직 D03 구현 commit/원격 CI 완료 기록은 없다. + +### 독립 리뷰에서 찾아 보완하는 사항 + +- **P1 legacy marker-only:** 이전 버전은 취소 실패 뒤 preferences를 지웠으므로 reset marker만 남은 상태가 실제로 가능하다. 새 journal/registry가 비었다는 이유로 Android 초기화를 완료 처리할 수 있었다. root는 이 상태를 unknown pending으로 기록하고, 사용자 원문 삭제와 알림 확인을 분리하는 회귀를 추가했다. 최종 회귀 검증 대기다. +- **P2 손상 journal:** malformed JSON/부분 first pending이 매번 같은 예외로 끝나면 앱이 영구히 시작되지 않는다. 원래 D03 전담이 가능한 registry/A14 읽기 전용 관측에서 최소 소유권을 재구성하고, 원본 quarantine·복구 중 표식·새 journal 검증의 crashwindow를 처리하는 중이다. I/O 오류, 지원하지 않는 버전, 의미적으로 불가능한 완료 상태를 마음대로 덮지 않는다. durable reset 의도나 현재 명시 요청이 없으면 사용자 DB/키 삭제를 시작하지 않는다. + +이 두 리뷰 사항의 최종 재현·회귀, 최종 source freeze/analyze/full/CI, Android/iOS 실제 전달·중단 재시작·백업 제외 read-back은 남아 있다. 이슈는 OPEN을 유지한다. + + +## 2026-09-24 최종 로컬 구현·검증 + +중간 기록의 두 리뷰 사항을 수정했다. legacy reset marker만 남은 상태는 unknown pending을 유지한다. JSON/UTF-8 문법 손상은 실제 registry와 A14 관측으로 최소 소유권을 재구성하며, I/O 실패·미래 버전·모순된 의미 상태는 덮어쓰지 않는다. 복구 표식→원본 격리→새 journal 검증→복구 표식 제거 순서로 중단을 복구한다. 독립 reset 의도나 현재 명시 요청 없이는 DB/키/사용자 데이터를 삭제하지 않는다. journal 자체의 unknown 또는 legacy 표식이 남으면 다음 bootstrap에서 재관측하고, 확인된 journal 저장 뒤에만 legacy 표식을 제거한다. 일시적 관측 실패·표식 제거 실패·새 owner 재시도도 검증했다. Android 관측 한계나 매핑 불명확 상태를 성공으로 바꾸지 않는다. + +- 최종 제품 및 테스트 소스: 전체 **872개**, 손상 복구 집중 **71개** 통과. coverage **87.99% (12253/13926)**. analyzer, generated/local-only 정책 통과. +- 고정한 **579개 파일**을 root가 재해시했다. 제품 및 test/ 소스 변경은 없고, host QA probe의 fake native observation만 명시적으로 unknown으로 수정했다. 최초 probe 실패는 iOS-empty 대역에 Android-unknown 기대를 적용한 설정 불일치였으며 제품 테스트 통과로 숨기지 않는다. +- 수정한 QA probe로 실제 별도 호스트 프로세스 **SIGKILL→새 프로세스 복구 11지점**을 모두 통과했다. 기존 6지점과 partial-first-pending, recoveryMarked, quarantinedPending, recoveryVerified, recoveryPublished를 포함한다. 추가 5지점은 손상 bytes를 명시적으로 준비한 fixture다. 실제 write syscall 중단·전원손실·모바일 OS 전달을 검증했다는 뜻은 아니다. +- 실제 SDK Swift typecheck, Android36/Flutter Kotlin compile, manifest/Python 정책 11개 통과. KO/EN partial/complete 200% text 위젯 4개를 직접 확인했다. +- 전체 로그, source digest 및 검증 범위는 `plans/audit-2026-09-23/d03-final-validation.json`과 `artifacts/d03/final/`에 저장했다. 최종 QA-only 수정 이후 analyzer도 재실행했다. + +원격 CI·실제 Android/iOS 전달/중단 복구/백업 제외 확인·병합·출시는 별도 단계다. 모바일 기기 수용조건이 남아 이슈는 OPEN을 유지한다. D01의 일반 손상 DB 복구 및 D02 후보 검증은 후속 범위이며, 이번에는 D03에 필요한 DB/DI 이전 복구 화면을 연결했다. + +구현 커밋: `5f3adf11aaaa3e36d6e4e25c41be6c06e4105480` — 제품/테스트/QA 49개 파일. 생성 산출물은 포함하지 않았다. diff --git a/plans/audit-2026-09-23/issues/U02.md b/plans/audit-2026-09-23/issues/U02.md new file mode 100644 index 00000000..b89dfffc --- /dev/null +++ b/plans/audit-2026-09-23/issues/U02.md @@ -0,0 +1,172 @@ +# [P1][U02] 일정 시간대 선택과 기기 환산 시각을 생성·편집·상세·반복 검토에서 일관되게 제공 + +상태: 전담 3문답 및 추가 근거·권한 보충 완료. 상세 이슈 본문이며 구현·테스트 통과 보고가 아니다. +전담: `/root/grill_u02`. 적용 skill: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +고정 조사 SHA: `0efdd3adb28293d3ff9f6b80bcd9091229384bd5`. + +## 문제와 사용자 영향 + +Schedule은 named time zone과 선택 occurrence offset을 저장하지만 생성·편집 화면에서 시간대를 고르는 동작이 없다. 기기와 다른 시간대의 약속을 정확하게 입력하기 어렵고, 달력 상세와 홈에서는 원래 시각과 기기 환산 시각을 구별할 수 없다. 서울 2026-09-25 09:00 약속이 뉴욕에서 2026-09-24 20:00에 해당해도 날짜 차이를 설명하지 못한다. 모델에 timeZoneId가 있다는 사실만으로 이 제품 요구가 구현된 것은 아니다. + +U02는 [A10 #608](https://github.com/DevKor-github/OnTime-front/issues/608)의 공통 civil/instant 해석 및 최소 UI를 재사용하여 선택·취소·확인·표시·접근성의 사용자 흐름을 완성하고 검증한다. A10과 다른 resolver, 별도 시간대 정책, 두 번째 DB writer를 만들지 않는다. 같은 변경으로 두 이슈의 수용조건을 충족할 수 있으나 이슈별 증거와 미완료 조건은 따로 기록한다. + +## 조사 범위와 증거의 고정 + +- 읽기 전용 `git rev-parse HEAD`가 위 SHA임을 확인했다. `git diff --`로 아래 제품 파일과 기존 UI 테스트가 고정본과 같음을 확인했다. 명령은 조사만 수행했으며 stage/commit/checkout/fetch/push를 실행하지 않았다. +- `CONTEXT.md`의 Schedule Time Zone, Nonexistent Schedule Time, Ambiguous Schedule Time 및 ADR0021의 기존 요구를 읽었다. A10 4문답 이후 root가 작성한 ADR0021 clarification/CONTEXT 보충은 별도 WIP 결정 근거이며 위 고정 SHA에 이미 존재한다고 주장하지 않는다. +- 병행 D03의 초기화·저널·startup 코드, 변경된 ARB, 테스트 수치·CI는 U02 구현이나 검증 증거가 아니다. 이 전담은 Flutter 명령과 제품 소스 수정을 실행하지 않았다. + +| 고정 소스 | 확인 사항 | +|---|---| +| [날짜·시각 폼:31](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/schedule_create/schedule_date_time/screens/schedule_date_time_form.dart#L31) | 날짜/시각 picker와 반복 입력을 제공하지만 zone 선택은 없다. 단발 DST gap/overlap 표시는 이미 일부 있으며 새 정책으로 버리지 않는다. | +| [폼 이벤트](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/schedule_create/bloc/schedule_form_event.dart), [등록 handler:30](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/schedule_create/bloc/schedule_form_bloc.dart#L30) | zone 변경 event/handler가 없다. UI만 추가하고 zone을 저장 초안에 전달하지 않으면 불충분하다. | +| [초안 생성:96, 편집:121](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/domain/use-cases/load_schedule_form_draft_use_case.dart#L96) | 생성은 LocalTimeZoneService, 편집은 저장된 zone/offset을 사용한다. 생성 초기값과 사용자의 명시적 선택을 구분해야 한다. | +| [기기 zone 조회](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/core/services/local_time_zone_service.dart) | 실패/빈 값에 UTC fallback이 있다. A10의 명시적 unknown 처리 없이는 UI에 감지 성공으로 제시하면 안 된다. | +| [시각 Cubit:62](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/schedule_create/schedule_date_time/cubit/schedule_date_time_cubit.dart#L62), [state:34](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/schedule_create/schedule_date_time/cubit/schedule_date_time_state.dart#L34) | 날짜·시각 변경 때 offset 후보를 갱신하지만 zone 변경 API가 없다. 비동기 인접 검사와 recurring 우회 검증이 있어 새 전이와 stale 결과를 함께 다뤄야 한다. | +| [폼 state:149](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/schedule_create/bloc/schedule_form_state.dart#L149) | offset null이면 device offset을 채우는 경로가 있다. A10의 명시 해석 계약으로 막아야 하며 UI에서 선택했다고 가정하지 않는다. | +| [달력 상세:173, 시각 열:195](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/calendar/component/schedule_detail.dart#L173) | scheduleTime만 전달·표시하며 zone/device equivalent가 없다. Hm과 영어 a를 함께 사용하는 기존 포맷도 공통 표시 계약으로 정리한다. | +| [홈 카드:108](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/home/components/todays_schedule_tile.dart#L108) | 원래 civil의 시각만 표시하고 다른 기기 날짜/zone을 설명하지 않는다. countdown 수정 자체는 A10과 공유한다. | +| [반복 검토:71](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/recurring/recurrence_review_sheet.dart#L71) | 첫 일정/준비 시작을 표시하지만 양쪽 zone과 날짜를 일관된 표기로 연결하지 않는다. | +| [알림 내용 구성:479](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/domain/entities/alarm_entities.dart#L479) | detailed 선택과 다른 zone 내용을 이미 구성한다. U02 때문에 private 기본 알림에 일정·zone 원문을 추가하지 않는다. | +| [기존 폼 widget test](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart), [상세 test](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/test/presentation/calendar/component/schedule_detail_test.dart) | picker 저장·KO 날짜·DST overlap 일부와 상세 동작은 검사한다. zone 검색→선택→실제 저장→다른 기기 표시를 증명하지 않는다. | + +## 확정 제품 계약 + +### 오프라인 시간대 선택 + +1. 번들 tzdb에서 지원되는 IANA zone 전체를 도시명/식별자로 검색한다. 번들 KO/EN 표시 이름을 사용할 수 있으며 번역·검색·시간대 규칙을 받기 위한 서버/API/위치 조회는 도입하지 않는다. 미번역 항목은 식별자로 명확하게 찾을 수 있어야 한다. +2. 현재 기기 시간대와 현재 draft 선택을 상단에 구별한다. device 조회 실패는 실패 상태로 표시하고 UTC로 가장하지 않는다. UTC는 사용자가 직접 선택할 수 있다. +3. offset만 고르는 목록, CST 같은 중의적 약어만 있는 목록, 기기 설정 화면으로 보내는 동작으로 대체하지 않는다. 검색 결과 없음에서도 입력 수정/취소가 가능하다. +4. 기존 civil 날짜·시각을 유지하면서 선택 zone의 occurrence를 다시 해석한다. 선택은 초안에만 적용한다. picker 취소는 선택 직전 초안을 보존하고 DB·revision·OS 예약에 영향을 주지 않는다. +5. alias/canonical 처리는 A10/D05의 호환 계약을 재사용한다. 저장된 역사적 zone 원문을 목록 열기나 조회만으로 정규화하지 않는다. 미지원 저장 zone도 원문과 수정 필요 이유를 표시한다. +6. 최종 저장에서 변경 전/후 약속 instant와 기기 환산의 차이를 확인한다. 최초 생성은 이전 약속이 없으므로 선택한 civil/zone/occurrence 및 기기 환산을 확인한다. 의미가 다른 중복 확인 모달을 추가하기보다 실제 저장·반복 검토 흐름에 통합한다. + +### 화면별 표시 + +| 표면 | 필수 동작 | +|---|---| +| 생성·편집 폼 | 원래 civil 입력+선택 zone, device 감지/명시 선택의 구별, 해석 상태와 선택 가능한 occurrence를 보여준다. 다른 zone이면 현재 device equivalent가 보인다. | +| 최종 저장 확인 | 이전/새 약속과 다른 device 날짜를 구별한다. 선택 전후 civil이 같아도 실제 순간이 바뀌었음을 숨기지 않는다. 확인 취소는 입력을 보존한다. | +| 홈 일정·nearest | 원래 civil+zone이 주표시이며 device zone이 다르면 보조표시한다. 홈 오늘의 device 날짜와 원래 약속 날짜가 다르면 양쪽 날짜를 제공한다. countdown은 A10의 instant를 사용한다. | +| 월 달력 상세 | 배치는 원래 zone civil 날짜로 유지한다. 상세에서 원래 날짜/시각/zone과 device equivalent를 제공하여 다른 날짜처럼 보이는 이유를 설명한다. | +| 반복 검토 | 선택한 편집 scope, 첫 실제 회차·준비 시작과 유한한 변경 영향에 동일 표시 모델을 사용한다. 각 occurrence의 당시 offset을 사용하며 현재 offset으로 전체 회차를 표시하지 않는다. | +| 상세 열기·스크린리더 | compact 화면에서 모든 설명을 반복하지 않아도 주/보조 날짜·시각·zone의 의미를 명확하게 읽고 전체 값을 확인할 수 있어야 한다. 중요한 timezone 식별을 ellipsis만으로 숨기지 않는다. | +| OS 알림 | private 기본은 유지한다. detailed opt-in에서만 일정명과 기기와 다른 원래 zone을 허용한다. 장소·메모·준비 원문은 추가하지 않는다. 실제 전달 시각은 같은 occurrence instant에 연결된다. | + +- 서로 다른 zone은 같은 offset이어도 다른 zone이라는 사실을 보조표시한다. canonical alias가 같은 zone으로 해석되는 기준은 공통 A10/D05 계약을 사용한다. +- 날짜가 다르면 반드시 양쪽 날짜를 쓴다. zone은 식별 가능한 이름/ID를 포함하며 약어만으로 표시하지 않는다. +- offset은 해당 occurrence 기준이다. 현재 device offset이나 현재 날짜의 DST offset으로 과거/미래 표시를 계산하지 않는다. 저장된 초 단위 offset을 표시가 손실시키거나 더 정밀한 값이라고 오인시키지 않는다. +- KO/EN, 기기 12/24시간, 날짜 형식, 접근성 읽기 순서를 공통 formatter/presentation 모델로 맞춘다. U05 전체 문구 개편과 혼동하지 않고 U02가 추가·변경한 모든 문자열은 ARB에 넣는다. + +### 초안·비동기·오류 상태 + +- 최초 초안은 감지에 성공한 device zone을 기본값으로 캡처한다. 이후 기기 이동/resume은 device equivalent만 갱신하며 civil/선택 zone/offset을 자동 변경하지 않는다. +- 명시적인 zone/날짜/시각 변경은 이전 occurrence 선택, 겹침 검사, 반복 review 승인을 무효화한다. 최신 draft revision의 해석·검사만 반영하고 늦게 도착한 이전 결과는 폐기한다. +- 다시 해석한 결과가 유일하면 유일 후보를 표시할 수 있다. 이전 ambiguous offset을 새 zone/날짜에도 무조건 재사용하지 않는다. +- gap은 다음 유효 civil을 제안하되 자동 이동·저장하지 않는다. overlap은 두 발생의 offset/instant/device equivalent를 구별하고 명시 선택 전 저장을 막는다. unknown zone은 정확한 환산값을 꾸며내지 않는다. +- 반복의 occurrence/following scope, gap skip, first/second 정책, frozen/exclusion/override를 유지한다. 일반 단발 gap 차단을 반복 expansion 전체의 새로운 정책으로 덮지 않는다. 변경 범위의 실제 첫 회차/준비 시작/유한 회차를 새 review에서 확인한다. +- 과거/시작/완료는 자동 이동하지 않는다. 역사 offset 불확실 상태도 추측 확정하지 않는다. 저장 실패/취소는 입력을 보존하되 실제 commit이 성공한 뒤 오래된 draft를 다시 적용하지 않는다. +- 다른 편집/restore/reset 세대 충돌은 재검토한다. 최종 확인 전 DB·알림 mutation은 0이다. 확인 뒤 저장은 A08 원자 workflow, 알림 취소/재예약은 A15/D03 owner를 사용한다. 저장 성공과 알림 실패는 서로 다른 결과로 알리고 저장 전체를 실패한 것처럼 재삽입하지 않는다. + +## 검증 행렬 + +아래 항목은 요구사항이며 아직 실행·통과했다고 주장하지 않는다. 공통 A10 검증은 정확한 소스/환경/결과 링크로 재사용하고 새 U02 상호작용은 직접 검증한다. + +| ID | 시나리오/경계 | 기대 결과·필요 증거 | +|---|---|---| +| U02-01 | Android/iOS 비행기 모드 최초 실행 후 zone 검색 | 번들 도시/ID 검색 가능, 네트워크·위치 권한 필요 없음. 실제 release artifact로 기록. | +| U02-02 | KO 서울/EN Seoul/IANA Asia/Seoul 검색·빈 결과·긴 ID | 항목 식별, 선택, 지우기·취소 가능; 지원 목록 누락/overflow 없음. 번들 번역 없는 zone도 ID 검색 가능. | +| U02-03 | device 조회 성공·실패 및 명시 UTC 선택 | 자동/명시 상태 구별, 실패를 UTC로 표시하지 않음; 명시 UTC 선택 뒤 정상 저장 가능. | +| U02-04 | 서울 2026-09-25 09:00, 뉴욕 device | 원래 9/25 09:00과 device 9/24 20:00/각 zone; 달력 9/25·뉴욕 홈 오늘 9/24 의미 유지. | +| U02-05 | 다른 zone이 같은 offset인 날짜 | 원래 zone과 device equivalent 둘 다 식별; offset 같다고 다른 zone을 숨기지 않음. | +| U02-06 | Asia/Kathmandu +05:45, Lord Howe DST 30분 및 역사 초 offset fixture | 정시 offset 가정 없음; occurrence 기준 환산 및 저장 정밀도 보존. | +| U02-07 | 동일 civil로 Seoul→New York zone 변경·최종 확인 취소 | civil 유지, 새 instant 미리보기, 취소는 DB/revision/OS mutation 0 및 draft 입력 보존. | +| U02-08 | New York 2026-03-08 02:30 gap | gap 안내·다음 유효 civil 제안, 자동 03:30 저장 없음; 명시 수정 뒤 새 검증. | +| U02-09 | New York 2026-11-01 01:30 overlap | UTC-04/UTC-05, 05:30Z/06:30Z 및 device equivalent 구별; 선택 전 저장 불가. 각 선택 실제 DB read-back/재시작/backup 왕복. | +| U02-10 | overlap 선택 후 다른 zone/날짜·시각 변경 | 이전 선택/review 무효화; 유일 후보 자동 표시 가능, stale ambiguous 선택 재사용 금지. | +| U02-11 | 빠른 zone A→B→C, B의 느린 인접 검사/해석 결과가 마지막 도착 | C draft와 일치하는 결과만 UI·저장에 사용; 늦은 결과가 버튼 유효성/preview를 덮지 않음. | +| U02-12 | form 열린 중 device zone 변경/resume | 선택 civil/zone/offset 그대로, device 보조표시 갱신, revision 증가·자동 DB 저장 없음. | +| U02-13 | 실제 생성→DB→편집 재진입→다른 기기 zone | zone/선택 offset과 원래 civil 유지; 실제 use case/repository/Drift 경계 확인, fake event 검사만으로 대체하지 않음. | +| U02-14 | 단발 저장 실패·commit 성공 후 알림 실패·빠른 저장 두 번 | 입력 보존/재시도, 중복 schedule 없음; 저장 receipt와 delivery 실패 구별, A08/A15 공통 계약 유지. | +| U02-15 | 검토 중 다른 편집/restore/reset | baseline/generation 충돌을 재검토하고 늦은 확인이 새 DB/세션을 덮지 않음. | +| U02-16 | recurring occurrence/following zone 변경 | 새 첫 회차·준비 시작·유한 영향 검토, slot ownership/override/exclusion/gap skip/count/frozen 유지; 과거·시작·완료 이동 없음. | +| U02-17 | 취소 후 반복 폼 돌아오기, review 이후 입력 변경 | 취소 전 입력 보존, 변경 뒤 기존 review 승인 재사용 금지, 저장은 새 review 기준. | +| U02-18 | unknown/alias/과거 explicit offset 및 null 역사 offset | 원문 보존, 지원 해석과 불확실성 구별, 조회만으로 canonical 재작성/추정 offset 저장 없음. | +| U02-19 | KO/EN 각각 12시간/24시간, device와 schedule 날짜 다름 | 주/보조 시각 포맷 일관, AM/PM 중복·혼합 없음, 양쪽 날짜 생략 없음. | +| U02-20 | 작은 320 logical px 화면/일반 화면·200%글꼴·긴 이름/ID·키보드 검색 | scroll/reflow로 검색결과·선택·취소·저장/오류 action 접근, overflow/잘림 때문에 의미 손실 없음. | +| U02-21 | VoiceOver/TalkBack | 선택 여부·원래/기기 시간대·첫/둘째 발생·오류/다음 행동을 정확한 순서로 읽으며 키보드/포커스가 막히지 않음. 실제 OS 접근성 확인. | +| U02-22 | private/default와 detailed, 기기 zone 변경 후 알림 재예약 | private 내용 누출 0, detailed는 이름/다른 원래 zone까지만, 취소 실패 pending 유지·중복 예약 없음. 실제 OS read-back/수신 증거. | +| U02-23 | 실제 Android/iOS에서 zone 설정 변경·앱 재시작·기기 간 backup restore | civil/zone/offset/instant·화면·OS target 시각이 일치. local mock과 OS 검증을 구별하고 미실행이면 미완료. | + +## 상세 수용조건과 완료 기준 + +- [ ] zone 검색/선택/취소/명시 확인을 실제 생성·편집 폼과 저장 workflow에 연결한다. standalone demo나 DTO 추가로 완료하지 않는다. +- [ ] 생성·편집 확인, 홈/nearest, 달력 상세, 반복 검토에 공통 원래 시각+device equivalent 표시를 적용하고 양쪽 날짜·occurrence offset을 정확히 전달한다. +- [ ] gap/overlap/unknown, alias/역사 의미, device resume, stale 비동기 결과, 저장/세대 충돌 상태가 위 계약대로 동작한다. +- [ ] 실제 DB와 A08/A15/D03 경계에서 취소/재시도/실패/중복 방지를 확인한다. 알림 권한이나 상세내용 정책을 변경하지 않는다. +- [ ] U02가 변경한 문구를 ARB KO/EN에 두고 12/24시간·작은 화면·200%글꼴·스크린리더·실제 시각적 화면 증거를 확보한다. 폰트가 없는 fake widget만으로 시각 QA 완료를 주장하지 않는다. +- [ ] 관련 focused/full Flutter test, analyze, 필요한 generator/정책 검사, 동일 변경 소스의 CI를 기록한다. 다른 이슈의 테스트 숫자를 복사하지 않는다. ignored generated Dart를 커밋하지 않는다. +- [ ] 실제 Android/iOS zone 전환·재시작·알림과 cross-zone backup restore 증거를 남긴다. 실제 기기 부재나 simulator 한계는 구분하고 남은 필수 검증이 있으면 issue를 OPEN 유지한다. +- [ ] implementation SHA, 테스트 명령·실행 환경·결과, 실제 화면/OS receipt와 남은 제한을 본문에 갱신한 뒤 root가 GitHub 원문 read-back을 비교한다. 이 문서 작성만으로 해결/배포 완료 처리하지 않는다. + +## 의존성과 책임 경계 + +- **A10 [#608](https://github.com/DevKor-github/OnTime-front/issues/608)**: civil carrier/typed resolver, occurrence instant, 날짜 표면 의미, unknown 처리, rule-change 확인 및 최소 UI. U02는 같은 모델 위에서 상호작용·표시·접근성을 완성한다. 독립적인 상충 resolver를 만들지 않는다. +- **A08 [#601](https://github.com/DevKor-github/OnTime-front/issues/601), C01 [#600](https://github.com/DevKor-github/OnTime-front/issues/600)**: 원자 저장·revision·정식 workflow receipt. UI에서 DB/알림을 따로 write하지 않는다. +- **A15 [#594](https://github.com/DevKor-github/OnTime-front/issues/594), D03 [#595](https://github.com/DevKor-github/OnTime-front/issues/595)**: 변경 후 reconcile의 single owner, 취소 실패 보존과 재예약. D03 WIP가 완료됐다는 주장은 하지 않는다. +- **A11 [#589](https://github.com/DevKor-github/OnTime-front/issues/589)** 및 ADR0030: private 기본 알림과 detailed opt-in 제한. +- **D05 [#606](https://github.com/DevKor-github/OnTime-front/issues/606)**: unknown zone/alias/역사 offset/backup 호환. U02가 backup format/schema를 임의로 변경하지 않는다. +- **U08 [#597](https://github.com/DevKor-github/OnTime-front/issues/597), U05**: 공유 접근성/현지화 기준을 사용하되 U02 신규 UI의 QA는 이 이슈에서도 책임진다. U05 아직 미발행이면 번호를 꾸며 넣지 않는다. +- **C08**: 홈 날짜 경계·resume 조회 갱신. U02의 표시 계약은 적용하지만 조회 전체 작업을 완료했다고 대신 주장하지 않는다. + +Android/iOS local-only가 범위다. 웹은 개발·시각 검증 용도이며 제품 지원으로 확대하지 않는다. 온라인 tzdb/지도·위치/도시 API, 서버·로그인·클라우드 분석·동기화, 현재 기기 설정 강제 변경, 전체 recurring 정책 재설계는 도입하지 않는다. + +## 문서 보충 제안 + +기존 CONTEXT의 세 용어와 A10의 ADR0021 clarification으로 의미가 충분하다. 이번 선택 목록/초안/표시 결정은 이를 구체화한 가역적인 UI 계약이므로 새 ADR을 만들지 않는다. 이 전담의 소유 파일은 U02.md 하나이며 공유 CONTEXT/ADR는 수정하지 않았다. root는 필요한 경우 기존 Schedule Time Zone 용어의 예시에서 원래 약속과 device equivalent 구별을 보충하되 검색 알고리즘·컴포넌트·revision 같은 구현 세부를 용어 정의에 넣지 않는다. + +## 실제 전체 문답과 보충 + +질문자는 `/root/grill_u02`, 답변자는 root이며 root가 사용자를 대신해 판단했다. 사용자의 직접 답변으로 꾸미지 않는다. 각 질문의 답변을 받은 후 다음 질문을 전송했다. + +### Q1 — 시간대 선택 목록과 초안 + +**Agent 질문 원문** + +> U02 전담에서 grill-with-docs를 읽고 현소스/CONTEXT/ADR0021/A10을 조사했습니다. Q1: 시간대 선택 UI는 번들 IANA 전체 목록을 오프라인 검색(도시/식별자), 현재 기기 시간대와 현재 선택 시간대를 상단에 표시하고, 시간대 선택은 기존 날짜·시각(벽시각)을 유지한 채 새 발생 시각을 미리 보여주는 방식으로 확정할까요? 권장안은 예입니다. 선택 즉시 DB를 바꾸지 않고 form draft에서만 적용하며, 선택 취소는 원래 초안을 유지하고 최종 저장에서 약속 시각 변경을 확인합니다. UTC offset만 고르는 목록이나 기기 설정 변경 안내로 대체하지 않습니다. 현재는 timeZoneId 모델만 있고 선택 event/UI가 없으므로 새 form 계약이 필요합니다. + +**Agent 보충 원문** + +> U02 조사 보충: 방금 A10 문서와 ADR0021 clarification을 읽었습니다. A10에서 최소 zone UI/원래 civil+device equivalent/규칙 변경 확인까지 이미 수용한 상태이므로 U02는 별도 두 번째 resolver나 중복 흐름을 만들지 않고 A10의 공통 시간 해석·최소 UI를 확장/최종 검증하는 UX 이슈로 문서화하겠습니다. Q1은 A10 결정을 번복하려는 질문이 아니라 미정인 목록·검색·초안 상호작용을 고정하는 질문입니다. 고정 SHA는 배정된 0efdd3ad로 링크하고, git 실행 금지에 따라 직접 fixed blob diff 검증은 root에 요청할 예정입니다. + +**Root 답변 원문** + +> Q1 답변: 권고안을 채택한다. 번들 tzdb의 지원되는 IANA zone을 오프라인 검색하고 도시명/식별자로 찾을 수 있게 한다. 현재 device zone과 현재 draft 선택을 상단에 분명히 구별하되 조회 실패를 UTC로 가장하지 않는다. UTC는 사용자가 명시적으로 고를 수 있는 정상 선택이다. 단순 offset 목록이나 기기 설정으로 보내는 안내로 대체하지 않는다. 선택은 form draft에만 반영하고 기존 civil 날짜/시각을 유지한 뒤 A10 resolver로 새 occurrence를 미리 보여준다. 취소 시 시간대 선택 전 초안을 그대로 보존하고, 최종 저장에서 이전/새 약속 instant와 device 표시 차이를 명시 확인한다. 현재 UI/event가 없는 부분은 새 구현 필요로 기록한다. 도시명 번역을 위해 서버/API를 도입하지 말고 번들 현지화와 식별자를 사용한다. 지원 alias·canonical 처리 기준은 D05/A10의 호환 계약을 재사용하며 저장돼 있던 역사적 zone 원문을 조회/목록열기만으로 바꾸지 않는다. root가 A10 #608의 실제4문답과 ADR0021 보충을 반영했으므로 별도 모순정책을 만들지 말 것. + +### Q2 — 주/보조 시각과 각 화면·알림 경계 + +**Agent 질문 원문** + +> U02 Q2: 화면별 주/보조 시각 표기를 어떻게 고정할까요? 권장: 생성·편집 확인, 홈 일정/가장 가까운 일정, 달력 상세, 반복 검토에서 원래 civil 날짜·시각+zone 식별 가능한 이름을 주표시하고, device zone이 다르면 같은 UTC offset이어도 기기 날짜·시각+zone을 보조표시합니다. 날짜가 다르면 반드시 양쪽 날짜를 쓰고 약어(예:CST)만으로 zone을 식별하지 않습니다. 시각의 offset은 현재 시점이 아니라 해당 occurrence의 실제 offset입니다. 동일 zone이면 불필요한 중복 줄은 생략하지만 원래 zone은 확인 가능해야 합니다. 알림은 기존 private 기본값을 유지하고 detailed opt-in에서만 일정명과 다른 원래 zone을 허용하며 장소/메모/준비내용은 추가하지 않습니다. KO/EN·기기 12/24시간·스크린리더·200% 글꼴을 공통 formatter/presentation 모델로 일관화하고, 홈 오늘=device 날짜/월 달력=원래 civil 날짜라는 A10 의미를 설명 가능한 짧은 표기와 상세 접근으로 유지할까요? + +**Root 권한 보충 원문** + +> 보충 해석에 동의한다. git 금지 범위를 명확히 한다: git show/rev-parse/diff/status 같은 읽기 전용 조회는 허용한다. stage/commit/checkout/reset/fetch/push 등 상태 변경은 금지다. 따라서 고정0efdd3ad blob의 근거를 직접 검증하고 문서화하라. 제품코드·Flutter실행 소유권 제한은 유지한다. + +**Agent 검증 보충 원문** + +> U02 소스 고정 확인: 읽기 전용 git rev-parse는 0efdd3ad였고 해당 SHA 대비 schedule_create 전체, schedule_detail.dart, todays_schedule_tile.dart, CivilTimeResolver, LocalTimeZoneService의 diff는 비었습니다. 본문에 fixed blob 링크를 사용할 수 있습니다. 공통 ADR0021/CONTEXT는 A10 WIP 문서 보충을 별도 결정 근거로 표시하고 D03 변경/test 숫자는 U02 증거에 포함하지 않습니다. + +**Root 답변 원문** + +> Q2 답변: 권고대로 확정한다. 생성·편집 최종 확인/홈 일정 및 nearest/달력 상세/반복 검토에서 원래 civil 날짜·시각과 식별 가능한 zone을 주표시한다. device zone이 다른 경우 동일 offset이어도 device equivalent를 보조표시한다(동일 순간이지만 다른zone이라는 사실을 숨기지 않음). 날짜가 다르면 양쪽 날짜를 빠뜨리지 않고 CST 같은 중의적 약어만으로 표시하지 않는다. offset은 해당 occurrence 기준이며 현재 기기 offset을 재사용하지 않는다. 동일 zone 중복줄은 생략 가능하되 원래 zone 확인 경로는 유지한다. 공통 formatter/presentation 모델로 KO/EN·12/24시간·스크린리더·200%글꼴을 맞추고 홈오늘/월달력 날짜 의미 차이는 짧은 표기+상세 설명으로 연결한다. OS 알림은 A11/ADR0030의 private 기본값과 detailed opt-in 경계를 유지하고 준비/장소/note 원문을 추가하지 않는다. 초·분 단위 역사 offset의 저장 의미를 표시 포맷 때문에 손실시키지 말고 A10/D05 검증 기준과 맞춰라. 각 화면에 동일한 긴 설명을 반복하기보다 기본 정보와 접근 가능한 상세확인을 일관되게 제공한다. + +### Q3 — draft 수명과 DST·반복·경쟁 + +**Agent 질문 원문** + +> U02 Q3: 편집 도중 device zone 변경·빠른 zone 전환·DST 선택·반복 범위의 상태 전이를 고정할까요? 권장: 첫 draft는 감지한 device zone을 기본값으로 캡처하되 이후 resume/device 이동은 device equivalent만 새로 계산하고 사용자의 civil/선택 zone/offset을 덮지 않습니다. zone·날짜·시각을 명시 변경하면 이전 offset/겹침검사/반복검토 승인을 무효화하고 가장 최신 draft의 해석 결과만 적용합니다(늦은 비동기 결과 폐기). gap은 다음 유효 시각 제안+명시수정 전 저장차단, overlap은 두 발생의 offset과 기기 환산 시각을 보여준 뒤 명시선택합니다. 반복은 기존 scope 선택(이번/이후 등)·first/second 정책·gap skip·frozen/exclusion/override를 유지하고, zone 변경으로 달라지는 첫 회차/준비시작/영향 회차를 새 review에서 확인하며 과거/시작/완료는 이동하지 않습니다. 저장 실패/취소는 입력 보존, 다른 편집/restore/reset 세대 충돌은 재검토하며 확인 전 DB·알림 변경은 0으로 정할까요? + +**Root 답변 원문** + +> Q3 답변: 권고안을 채택한다. 최초 draft 기본 zone은 성공적으로 감지한 device zone을 캡처하고, 이후 device 이동/resume은 보조표시만 갱신하며 civil/선택zone/선택offset을 조용히 덮지 않는다. 명시적인 zone/날짜/시각 변경은 이전 occurrence 선택 및 겹침·반복 review 승인을 무효화하고 최신 draft revision에 해당하는 비동기 결과만 적용한다. 단, 다시 해석해 유일 occurrence라면 자동으로 유일 후보를 표시할 수 있으나 이전 ambiguous 선택을 무조건 재활용하지 않는다. gap은 제안만 하고 명시수정 전 저장금지, overlap은 서로 다른offset/instant/device환산을 구별해 선택하게 한다. 반복 기존scope/first-second/gap skip/frozen/exclusion/override 규칙을 보존하고 선택범위의 실제 변경 영향(첫회차·준비시작·유한회차)을 새review에서 보여준다. 과거·시작·완료는 자동이동하지 않는다. 저장 실패·선택취소는 입력을 보존하되 성공한 실제commit 이후 과거draft를 재적용하지 않는다. 다른편집/restore/reset세대충돌은 재검토하며 최종확인 전 DB/알림 mutation 0이다. A08의 원자저장과 A15/D03의 취소후재예약 경계를 사용하고 UI 자체에 별도writer를 만들지 않는다. 충분하면 실제3문답·보충전체와 UI별수용기준/테스트행렬/원격Issue연결을 문서로 동결해라. 추가중요결정이있으면 하나씩 물어라. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index cbee7629..896ba5a6 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -358,18 +358,27 @@ "`local_data_reset_service.dart:19-25`; `local_data_lifecycle.dart:45-69`; `cancel_schedule_alarm_use_case.dart:25-37`; `cancel_all_alarms_use_case.dart:19-41`; `reconcile_alarms_use_case.dart:549-570`; 관련 테스트 `:951-975`", "이미 없음과 실제 취소 실패 구분, 실패 기록 보존·재시도·OS read-back. 중간 강제 종료 후 정리 완료. reset marker는 검증 가능한 종료 상태까지 유지." ], - "status": "implementing", + "status": "code_verified_ci_device_pending", "agent": "/root/grill_d03", "issue_number": 595, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/595", "decisions_file": "plans/audit-2026-09-23/issues/D03.md", - "implementation_commit": null, - "pr_url": null, - "verification": [], - "blockers": [], + "implementation_commit": "5f3adf11aaaa3e36d6e4e25c41be6c06e4105480", + "pr_url": "https://github.com/DevKor-github/OnTime-front/pull/585", + "verification": [ + "Final full872 and focused71 passed; coverage87.99% (12253/13926); analyze/generated/local-only passed", + "Product/test freeze unchanged; QA-only explicit unknown fixture correction then all11 host SIGKILL cases passed", + "SDK Swift typecheck/Kotlin compile; Python11/manifest passed; KO/EN 200% four widget PNGs visually reviewed" + ], + "blockers": [ + "Actual Android/iOS delivery, process interruption recovery and backup-exclusion device read-back pending", + "Remote CI and merge pending" + ], "sequence": 11, "readback_verified": true, - "body_sha256": "9b64e0d110e831fabf030cd3408f496784fe2d5e189d9cdbd76edd1ba5bb6e65" + "body_sha256": "290e625b9510c275e775bd8301b804ce2a57cae09716a6c7d469ec5b6dc0622f", + "progress_file": "plans/audit-2026-09-23/d03-journal-progress.json", + "validation_file": "plans/audit-2026-09-23/d03-final-validation.json" }, { "audit_id": "A06", @@ -597,16 +606,18 @@ "`lib/domain/entities/schedule_entity.dart:56-74`; `schedule_bloc.dart:128,664`; `lib/presentation/home/components/todays_schedule_tile.dart:145-158`", "비교·타이머는 absolute instant, 입력·표시는 civil time으로 통일. Seoul/New York 기기 시간대 전환, DST gap/overlap, 재시작 전후 같은 일정의 실제 시작 순간이 같아야 함." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_a10", + "issue_number": 608, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/608", "decisions_file": "plans/audit-2026-09-23/issues/A10.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 21 + "sequence": 21, + "readback_verified": true, + "issue_body_sha256": "f5048dafeacc2cfe42cfc05aaa986fe0f4bf7279044b00c787b654f9373bb7ab" }, { "audit_id": "U02", @@ -618,16 +629,18 @@ "일정 시간대 선택과 다른 기기 시간대에서의 환산 시각을 표시한다.", "`schedule_date_time_form.dart:29-187`, `schedule_detail.dart:175-176,209-215`; CONTEXT 382-388의 제품 요구와 차이. 생성·편집·상세·알림의 시간대 의미를 통일." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_u02", + "issue_number": 609, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/609", "decisions_file": "plans/audit-2026-09-23/issues/U02.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 22 + "sequence": 22, + "readback_verified": true, + "issue_body_sha256": "687bb228690a05af9dbee439d9f886d763879b1415421876e2204cdd81b67762" }, { "audit_id": "C08", From 6b5469a7eba6aa59a68bbbca07d0955dad1f63aa Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 10:42:03 +0900 Subject: [PATCH 35/59] docs: define lifecycle-aware nearest schedule query --- CONTEXT.md | 4 + plans/audit-2026-09-23/README.md | 4 +- plans/audit-2026-09-23/issues/C08.md | 198 +++++++++++++++++++++++++++ plans/audit-2026-09-23/state.json | 12 +- 4 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/C08.md diff --git a/CONTEXT.md b/CONTEXT.md index d51aedba..f3ff0894 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -21,6 +21,10 @@ _Avoid_: Server result, analytics event, transient completion screen Past and completed Schedule details retained locally until the user explicitly deletes the Schedule. _Avoid_: Server archive, analytics history, automatic retention window, score aggregate +**Nearest Upcoming Schedule**: +The unfinished Schedule whose resolved occurrence instant is the earliest at or after the current instant. +_Avoid_: Today's Schedule, active Schedule Preparation Session + **Local Punctuality Score**: The percentage of eligible Schedule Outcomes completed On Time since the latest Punctuality Score Reset. _Avoid_: Server score, lifetime score, reward points, zero before first result diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 44048473..a204f3b8 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -41,7 +41,9 @@ | U02 | [#609](https://github.com/DevKor-github/OnTime-front/issues/609) | 실제3문답·오프라인 zone 선택/기기 환산·DST/변경 재검토,23개 검증 시나리오 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **23/64개**를 게시하고 원격 본문 동일성을 확인했으며 **41개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 후 최종872 tests/87.99% 및 host kill11 검증을 통과했고 CI·기기 확인을 진행한다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +| C08 | [#610](https://github.com/DevKor-github/OnTime-front/issues/610) | 실제3문답·현재 instant/세션보존/수명주기 갱신·오류/제한 구분,32개 검증 시나리오 확정 | + +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **24/64개**를 게시하고 원격 본문 동일성을 확인했으며 **40개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 후 최종872 tests/87.99% 및 host kill11 검증을 통과했고 CI·기기 확인을 진행한다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/C08.md b/plans/audit-2026-09-23/issues/C08.md new file mode 100644 index 00000000..20c2b262 --- /dev/null +++ b/plans/audit-2026-09-23/issues/C08.md @@ -0,0 +1,198 @@ +# [P1][C08] 가장 가까운 일정 조회를 현재 시각·수명주기에 맞춰 갱신하고 오래된 결과와 조회 실패를 구분 + +## 상태와 목적 + +- 감사 ID: C08 · 우선순위 P1 · 상태: 전담 grill 완료 / 구현·검증 예정. +- 전담 서브에이전트: `/root/grill_c08`. 사용 스킬: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. 사용자의 명시적 위임에 따라 질문은 root에게 한 번에 하나씩 보냈고 각 답을 받은 뒤 다음 질문을 진행했다. 실제 3문답과 보충 메시지는 아래에 그대로 보존한다. +- 코드 증거 기준: `0efdd3adb28293d3ff9f6b80bcd9091229384bd5`. 조사 시작 당시 작업 트리에는 별도 D03 미커밋 구현이 있었으므로 이 문서의 기존 동작 증거는 고정 커밋을 사용했다. A10의 합의는 정책 근거이며 해당 구현 완료를 뜻하지 않는다. +- 범위: Android/iOS의 로컬 일정 조회·선정·표시와 이를 사용하는 현재 앱 수명주기. 서버, 로그인, 원격 시각·위치 조회, 원격 analytics는 도입하지 않는다. +- 문제: 앱이 며칠 동안 살아 있거나 백그라운드에서 돌아오면 구독 시작 시각에 고정된 범위 때문에 지금 가장 가까운 일정이 빠진다. 조회 실패가 `null`로 흡수되면 실제 일정이 있는데도 빈 상태가 되고, 갱신을 단순 재구독으로 붙이면 진행 중 준비 세션을 정리하는 기존 분기와 충돌한다. +- 결과: 현재 시각에 맞는 미래 후보를 지속해서 재평가한다. 빈 목록, 읽기 실패, 아직 완전히 탐색하지 못한 결과를 구분하며 이전 요청·이전 데이터 세대의 결과가 현재 화면이나 준비 실행을 바꾸지 못한다. + +## 현재 코드에서 확인한 사실 + +아래 링크는 모두 고정 커밋의 실제 소스다. 제품 결함과 미실행 검증을 혼동하지 않는다. + +| 근거 | 확인한 동작과 영향 | +| --- | --- | +| [GetNearestUpcomingScheduleUseCase L26](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/domain/use-cases/get_nearest_upcoming_schedule_use_case.dart#L26) | `call()` 시작의 `DateTime.now()`를 한 번 캡처하고 `now`부터 `now + Duration(days: 2)`를 구독한다. 시간이 지나도 양 끝이 이동하지 않는다. 목록의 첫 `notEnded`를 선택하며 준비 조회 예외는 `yield null`로 바꾼다. | +| [ScheduleRepositoryImpl L52](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/data/repositories/schedule_repository_impl.dart#L52) | `watchSchedulesByDate`는 구독 진입 시 한 번 반복 회차를 materialize하고 `schedule.scheduleTime`의 civil 값으로 고정 범위를 필터·정렬한다. 입력 범위가 낡으면 새 날짜 반복 회차도 이 구독 자체로는 갱신되지 않는다. | +| [App lifecycle L152](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/app/screens/app.dart#L152) | resume은 `SchedulePreparationTimeRefreshRequested`와 기존 알림 reconciliation 등을 실행한다. nearest 조회 범위의 새 평가를 명시적으로 요청하지 않는다. | +| [ScheduleBloc L212](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/app/bloc/schedule/schedule_bloc.dart#L212) | 재구독에 subscription revision을 쓰지만 새 nearest 도착 처리에는 조회 요청 자체의 revision이 포함되지 않는다. 느린 이전 준비 조회가 후속 요청보다 늦게 끝나는 경우까지 다루어야 한다. | +| [ScheduleBloc L232](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/app/bloc/schedule/schedule_bloc.dart#L232) | incoming이 null이거나 `scheduleTime.isBefore(now)`이면 persisted state를 clear하고 preparation timer와 현재 schedule을 정리한다. 다른 ID로 바뀔 때도 기존 persisted state를 지운다. 따라서 일반 갱신과 활성 세션 종료를 구분하지 않는 단순 연결은 위험하다. | +| [GetPreparationByScheduleIdUseCase L17](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/domain/use-cases/get_preparation_by_schedule_id_use_case.dart#L17) | preparation stream에서 non-null 값의 `.first`를 기다린다. 해당 준비가 없으면 명시적인 missing 결과 없이 계속 기다릴 수 있다. | +| [LoadSchedulesForWeekUseCase L10](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/domain/use-cases/load_schedules_for_week_use_case.dart#L10) | 입력 시각에서 Duration으로 주 시작/끝을 계산한다. 이것을 nearest instant query의 유일한 후보 범위로 재사용해서는 안 된다. Today/달력 civil 경계의 정밀한 처리는 A10과 맞춘다. | +| [Recurring materialize L649](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/data/repositories/recurring_schedule_repository_impl.dart#L649) | from/through/perSeriesLimit과 기존 slot/exclusion 의미를 가진다. C08 때문에 모든 미래 회차를 무한히 만들거나 보호된 회차를 다시 쓰면 안 된다. | +| [ScheduleEntity L74](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/domain/entities/schedule_entity.dart#L74) | `occurrenceInstantUtc`의 offset-null fallback은 device 해석 영향을 받을 수 있다. 단순히 getter 이름만 바꿔 호출하는 것으로 A10의 명시적 해석 계약이 충족되지는 않는다. | +| [CivilTimeResolver L87](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/core/time/civil_time_resolver.dart#L87) | unknown zone fallback 문제는 A10 소유다. C08은 A10의 resolved/선택 필요/unknown 결과를 소비해야 한다. | +| [ScheduleState L3](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/app/bloc/schedule/schedule_state.dart#L3) | 현재 enum은 initial/notExists/upcoming/ongoing/started다. 조회 실패·조회 제한을 `notExists`로 흡수하지 않는 결과 모델과 실제 UI 연결이 필요하다. | +| [HomeScreenTmp L128](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/home/screens/home_screen_tmp.dart#L128), [HomeScreen L32](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/lib/presentation/home/screens/home_screen.dart#L32) | nearest 소비 화면과 별도 주간/오늘 목록이 공존한다. 이름이 비슷하다는 이유로 오늘 목록을 nearest로 바꾸거나 달력 날짜 정책을 바꾸지 않는다. 실제 라우터의 사용 경로를 구현 때 검증한다. | +| [nearest 테스트](https://github.com/DevKor-github/OnTime-front/blob/0efdd3adb28293d3ff9f6b80bcd9091229384bd5/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart) | background week load 실패를 삼킨 뒤 빈 stream을 내보내는 1개 테스트만 있다. 수일 경과·resume·시계 변경·늦은 응답·진행 세션 보존을 증명하지 않는다. 새 계약과 충돌하는 실패 삼키기 기대값은 바꿔야 한다. | + +## 확정한 선정 의미 + +**Nearest Upcoming Schedule**은 현재 instant 이상인 해석 완료 occurrence instant를 가진 미완료 일정 중 가장 이른 일정이다. `Today's Schedule`이나 현재 활성 `Schedule Preparation Session`과 다른 개념이다. root가 승인한 glossary 문구는 문답 부록에 있다. 구현세부를 CONTEXT에 넣지 않으며 이 가역적인 조회 정책을 위해 별도 ADR은 만들지 않는다. + +1. 적격 후보는 `doneStatus == notEnded`이고 A10의 명시적 해석 결과가 resolved이며 `occurrenceInstantUtc >= nowUtc`인 일정이다. 동일 instant이면 안정적인 schedule ID 순서로 결정한다. DB iteration이나 Set 순서에 기대지 않는다. +2. Preparation Start Moment가 이미 지났어도 약속 instant가 미래면 후보에 남는다. 약속 instant가 지난 미진행 일정은 nearest에서 제외하고 기록·상태는 보존한다. 조회 때문에 자동 완료, 삭제, outcome 등록을 하지 않는다. +3. 48시간 제품 cutoff를 제거한다. 단발 일정이나 이미 materialize된 일정이 다음 주·다음 달에 한 개뿐이어도 선택할 수 있어야 한다. UI가 이를 '오늘'이라고 오표시하지 않아야 한다. +4. 반복 미래 전체를 무한히 materialize하지 않는다. 기존 bounded horizon/cap, candidate budget, slot/exclusion, 분할·override 및 frozen/started 회차의 의미를 보존한다. 범위별 탐색·재조회는 중단 가능해야 한다. +5. 탐색 cap/budget에 닿아 더 이른 미탐색 후보가 없음을 증명하지 못하면 이미 발견한 후보도 확정 nearest로 활성화하지 않는다. `queryLimited`로 구분하고 bounded retry를 제공한다. 근거 없이 전체 미래에 일정이 없다고 단정하지 않는다. +6. unresolved/unknown zone 등의 일정은 A10 원칙에 따라 해당 후보만 실행 대상에서 제외한다. 정상 후보를 계속 찾되 제외 사유와 수정 진입을 설명할 수 있어야 한다. 데이터 읽기 실패를 이런 의도적 제외와 혼동하지 않는다. +7. 홈 Today의 device civil day instant 구간과 월 달력의 원래 zone civil 날짜 정책은 A10을 유지한다. nearest를 달력의 좁은 civil 날짜 범위에서만 고르면 다른 시간대 후보를 누락할 수 있다. + +## 갱신·수명주기·동시성 계약 + +| 신호 | 요구 동작 | +| --- | --- | +| 최초 구독 | 현재 clock/zone/generation으로 새 평가. 상수로 박힌 실제 DateTime.now 대신 테스트 가능한 경계를 사용한다. | +| foreground의 device civil 날짜 변경 | 새 날짜 의미로 재평가한다. 다음 자정은 civil 날짜 구성으로 구하며 DST 날에 24시간을 단순 더하지 않는다. | +| 현재 후보의 appointment instant 경과 | 다음 날짜까지 기다리지 않고 다음 후보를 평가한다. equality에서는 여전히 적격이고 그 이후에는 제외한다. 경계 재예약의 0시간 busy loop를 막는다. | +| resume | 지나간 날·초 단위 이벤트를 재생하지 않고 현재 기준 한 번 평가한다. resume burst는 coalesce할 수 있지만 마지막 요청은 유실하지 않는다. | +| 수동 clock/zone 변경 | foreground 경계 timer와 최대 1분 간격의 가벼운 drift 확인으로 놓친 변경을 탐지하고 재평가한다. 매분 전체 DB 조회·반복 expansion을 하는 설계가 아니다. | +| 일정·준비 데이터 변경 | 현재 구독에서 관련 변경을 반영한다. 삭제/완료/시각·준비 변경으로 후보가 바뀌면 최신 데이터 기준으로 평가한다. | +| active session 종료·해제 | 소유권이 해제된 뒤 최신 후보를 적용한다. 종료 전에 쌓인 옛 조회 결과를 적용하지 않는다. | +| pause/dispose | pause 동안 heartbeat 중지. dispose에서는 모든 timer/subscription을 취소한다. 취소되지 않는 underlying Future의 결과는 currentness 검사로 폐기한다. | +| restore/reset/DB 세대 변경 | 기존 구독·현재 결과·stale cache를 무효화한다. 이전 DB 응답이 새 프로필이나 복원 데이터에 반영되지 않는다. A15/D03 공통 lifecycle을 따른다. | + +- one-shot 경계 timer와 가벼운 drift 점검의 clock을 주입해 foreground 수일 유지, 앞뒤 시각 이동, 자정과 resume 경합을 재현한다. +- 요청마다 revision을 캡처하고 모든 await 뒤 request revision과 `LocalDataOperationGate` generation을 검사한다. 이전 준비 읽기, materialize, repository 이벤트는 emit/persist/navigation/notification을 만들 수 없다. +- query debounce/coalesce는 허용하지만 최종 요청을 반영해야 한다. A의 느린 응답이 B의 최신 결과를 덮는 상태가 없어야 한다. +- active/early-start session과 notification prompt는 nearest refresh로 교체하거나 persisted state를 clear하지 않는다. 그 소유자가 정상적으로 종료·해제한 뒤 최신 후보를 적용한다. +- 사용자의 삭제/완료/복원 때문에 세션 자체가 무효가 되면 기존 도메인 lifecycle이 처리한다. 'active 보호'를 이유로 실제 무효화를 숨기거나 삭제한 일정을 계속 실행하지 않는다. +- 이 조회 경로 자체는 새로운 start/finish, 단계 알림, OS 알림 예약을 재생하지 않는다. A06의 단계 알림 현재성·freshness와 A15의 단일 delivery owner를 우회하지 않는다. +- 단순 시각/zone refresh는 원래 약속 civil/offset·durable revision을 바꾸지 않는다. 필요한 반복 회차 materialization만 기존 transaction 의미로 허용하며 refresh마다 불필요하게 데이터를 다시 쓰지 않는다. + +## 결과·오류·재시도 UX + +| 결과 | 의미와 사용자 동작 | +| --- | --- | +| `loading` | 최신 평가 진행 중. 영구 empty로 보이지 않는다. 기존 값은 동일 generation일 때만 stale 읽기 전용으로 잠시 표시할 수 있다. | +| `ready(schedule)` | 필요한 후보 탐색과 해당 준비 읽기가 성공했고 더 이른 미탐색 후보 문제도 해결된 현재 값. A10 해석 완료와 현재성 검증 이후에만 새 준비 시작에 사용할 수 있다. | +| `empty` | 현재 계약의 검색범위에서 적격 후보가 없다고 확인한 값. 조회 실패·budget 제한·missing preparation을 이 상태로 바꾸지 않는다. | +| `recoverableError` | repository watch, 반복 materialize, 선택된 nearest 준비 읽기의 실패 또는 missing 불변식 오류. safe reason, KO/EN 설명, 재시도를 제공한다. missing은 편집/복구 진입도 고려한다. 더 늦은 정상 후보로 조용히 우회하지 않는다. | +| `queryLimited` | bounded 탐색의 cap/budget로 후보 선정의 완전성이 아직 확인되지 않음. 다음 bounded 평가 또는 다시 시도를 제공한다. 확정 nearest로 실행하지 않는다. | + +- invalid-zone 설명은 정상 ready 후보와 함께 표시할 수 있다. 잘못된 하나 때문에 정상 앱 사용을 모두 막지 않는다. +- preparation은 명시적 일회성 local read의 found/missing/error를 사용한다. `.where(nonNull).first`로 없는 값을 무기한 기다리지 않는다. 실제 진행 중 읽기 자체를 임의 성공이나 empty로 바꾸지 않는다. +- 마지막 검증값을 보여줄 때는 stale임을 알리고 읽기 전용으로 제한한다. 새 start/자동화에는 사용하지 않는다. 동일 generation이라는 사실만으로 최신 데이터임을 가정하지 않는다. +- restore/reset은 stale 표시까지 즉시 폐기한다. 개인정보가 포함된 이전 데이터가 새 profile/generation에 노출되어서는 안 된다. +- 재시도는 오류 이전 파괴 작업을 반복하는 경로가 아니라 현재 기준의 새로운 bounded query다. 데이터 원본과 기존 활성 세션은 보존한다. +- platform/DB 예외 원문, 일정명, 장소, 준비명, 전체 payload를 로그나 사용자 오류에 넣지 않는다. 진단은 안전한 코드와 비민감 현재성 정보만 사용한다. +- 로딩·오류·조회 제한·수정 진입과 재시도는 KO/EN, 작은 화면 및 200% 글자 배율에서 읽고 조작할 수 있어야 한다. 전체 디자인 재설계가 아니라 새 상태의 실제 표시·접근성을 포함한다. + +## 구현 작업 단위와 의존성 + +1. A10의 resolved occurrence와 civil/instant 소비자 계약을 연결해 nearest 후보 선정 함수를 정의한다. 표면별 날짜 의미를 섞지 않는 repository/query 경계를 사용한다. 기존 civil TEXT를 UTC로 일괄 변환하지 않는다. +2. `GetNearestUpcomingScheduleUseCase`의 고정 now+2일·first notEnded·catch→null을 대체한다. 준비 found/missing/error, 검색 제한, stale generation을 표현한다. 무기한 non-null 대기 경로를 제거한다. +3. lifecycle/clock/zone 경계 신호와 cancel 가능한 구독·타이머 소유권을 연결한다. periodic drift tick은 가벼운 비교만 하고 변화가 있을 때 필요한 평가를 요청한다. +4. `ScheduleBloc`에서 query 상태와 활성 session 소유권을 구분한다. 현재 null/지난 civil 입력으로 persisted state를 지우는 분기에 새 typed query 결과를 그대로 넣지 않는다. notification prompt ownership 및 기존 A03/A06/A07/A15 처리를 보존한다. +5. 실제 routed nearest 화면과 준비 시작 경로에 ready/loading/error/queryLimited/stale 제한을 연결한다. Today·달력은 A10 정책에 맞춘다. 인터페이스 추가만으로 완료하지 않는다. +6. 아래 행동 중심 테스트를 구현하고 실제 프로세스를 수일 기다리지 않는 주입 clock·fake lifecycle·통제된 pending Future로 검증한다. 마지막에 Android/iOS 실제 resume/설정 변경 증거를 확보한다. + +| 의존 이슈/정책 | 연결 기준 | +| --- | --- | +| [A10 #608](https://github.com/DevKor-github/OnTime-front/issues/608), ADR0021 | instant 해석·날짜 표면 의미·unknown zone·tzdb 확인 상태. 미구현 fallback을 그대로 사용한 C08만을 완성으로 닫지 않는다. | +| U02, A10 | 원래 zone 주표시 및 device 보조표시, 잘못된 zone 수정 진입. 이 문서 작성 시 U02 게시 번호는 추정하지 않는다. | +| [A06 #598](https://github.com/DevKor-github/OnTime-front/issues/598) | 시간 갱신과 실제 단계 알림 emission은 구분. resume/catch-up에서 과거 단계를 재알림하지 않는다. | +| [A07 #599](https://github.com/DevKor-github/OnTime-front/issues/599) | session start 성공/실패·재시도 의미 보존. nearest 재조회가 start 성공을 선반영하지 않는다. | +| [A09 #602](https://github.com/DevKor-github/OnTime-front/issues/602), [A15 #594](https://github.com/DevKor-github/OnTime-front/issues/594), [D03 #595](https://github.com/DevKor-github/OnTime-front/issues/595) | restore/reset generation, session 정리 소유권, 단일 alarm owner. 이전 Future를 native cancel했다고 과장하지 말고 결과 무효화와 owner 책임을 구분한다. | +| ADR0034 | 과거 notEnded를 nearest에서 제외하더라도 기록 자동 만료·삭제·완료 처리를 하지 않는다. | + +## 수용 조건 + +- [ ] 최초 구독 후 2일 이상 시간이 지나도 새로운 현재·미래 후보를 고른다. resume 없이 foreground로 살아 있는 경우도 동일하다. +- [ ] 48시간 밖의 단발·기존 materialized 미래 일정이 정상 후보로 나타난다. civil 정렬이 UTC instant 순서를 뒤집지 않는다. +- [ ] equality/경과·동률 ID·완료 제외·준비 시작만 지난 경우가 확정 정의와 일치한다. +- [ ] 반복 bounded 검색에서 cap/budget 한계를 숨기지 않는다. 미탐색의 더 이른 후보 가능성이 있으면 확정 ready가 아니다. +- [ ] 날짜·zone·clock 변경·resume burst·DB/preparation 변경·세션 해제마다 최종 최신 요청이 반영된다. dispose 후 timer/subscription·emit이 남지 않는다. +- [ ] 모든 await의 request revision/generation 검증과 old subscription 폐기를 통해 이전 결과가 새 화면·세션·DB에 영향을 주지 않는다. +- [ ] active/early session과 notification prompt를 단순 refresh가 종료·clear·교체하지 않는다. 실제 삭제/완료/restore 무효화는 기존 lifecycle대로 반영한다. +- [ ] repository/materialize/준비 읽기 실패 및 missing을 empty나 후순위 후보로 대체하지 않는다. 무기한 non-null 대기 없이 typed 오류·재시도·필요한 수정 진입을 제공한다. +- [ ] stale 값은 같은 generation의 읽기 전용으로만 표시하며 fresh 검증 전 새 start/자동화에 사용하지 않는다. restore/reset 시 stale도 폐기한다. +- [ ] A10의 invalid-zone 제외·설명과 정상 후보 공존을 지원하며 unknown 값을 UTC/device zone으로 몰래 바꾸지 않는다. +- [ ] refresh 때문에 start/finish/outcome/단계 알림/OS 예약이 재생되지 않는다. 단순 시각 변경이 약속 civil/offset·durable revision을 변경하지 않는다. +- [ ] 실제 앱의 nearest consumer에 loading/empty/error/queryLimited/stale 상태가 KO/EN과 200% 글자에서 구분되고 조작 가능하다. +- [ ] 아래 unit/repository/bloc/widget 통합 검증, analyze 및 관련 전체 회귀를 실행한 정확한 소스와 로그를 남긴다. 실행하지 않은 시나리오를 통과로 기록하지 않는다. +- [ ] Android/iOS 실제 lifecycle/수동 시각·zone 변경 검증을 별도 증거로 기록한다. fake clock·host test·CI는 모바일 OS 동작 증거로 대체하지 않는다. 의존 구현·기기 증거가 남으면 이슈를 OPEN 유지한다. + +## 검증 시나리오 + +모두 계획된 검증이며 현재 실행 결과가 아니다. 테스트는 실제 제품 경계의 expected behavior를 확인하고 구현 private 메서드 호출 횟수만 복제하지 않는다. + +| # | 준비 및 동작 | 기대 결과 | +| --- | --- | --- | +| 1 | D0 구독, D1/D3 일정, 프로세스 유지한 채 clock D3로 이동 | D0의 고정 범위 대신 D3의 최신 유효 후보 선택. | +| 2 | 다음 주 단발 일정 한 개만 존재 | 48시간 cutoff 없이 해당 일정 표시; '오늘' 오표시 없음. | +| 3 | 두 일정의 civil 순서와 UTC instant 순서가 반대 | UTC instant가 이른 일정 선택. | +| 4 | 같은 instant인 두 schedule, 입력 Set 순서를 바꿔 반복 평가 | stable ID 순서가 일관됨. | +| 5 | 후보 instant 직전·정확히 같은 순간·직후 | 직전/동일 적격, 직후 다음 후보. 0-delay loop·중복실행 없음. | +| 6 | 준비 시작은 과거이나 약속은 미래 | 후보 유지. 조회 자체가 start/단계 알림을 재생하지 않음. | +| 7 | 오래된 notEnded와 미래 일정 공존 | 미래 일정 선택; 과거 row/status/outcome·history 변화 없음. | +| 8 | 완료 일정이 가장 빠름 | 완료 제외 후 다음 미완료 선택. | +| 9 | foreground 자정, 월말/연말·DST 23/25시간 날 | 실제 device civil 경계로 평가, 24h 오프셋 누락 없음. | +| 10 | pause 후 수일 경과, resume 신호 여러 개 | heartbeat 중지, 현재 최종 평가 수렴, 놓친 일자/단계 replay 없음. | +| 11 | 같은 날짜 내 시계 앞으로/뒤로 변경 | 최대 1분 drift 확인 또는 더 이른 경계신호로 최신 후보; timer 연쇄/과거 단계 재알림 없음. | +| 12 | 서울→뉴욕 등 zone 변경으로 device 날짜가 달라짐 | 원래 약속 instant·civil 저장 불변, A10 표면 의미와 후보 일치. | +| 13 | 일정 생성·삭제·완료·시간수정·준비수정 | 현재 값 재평가; 원래 row와 준비 관계가 최신 기준으로 일치. | +| 14 | A 준비 읽기 pending, B 요청 성공 후 A 완료 | A 결과가 B state/persist/navigation/notification을 덮지 않음. | +| 15 | 이전 repository stream이 cancel 뒤 지연 emit | request revision으로 무시; 새 subscription만 현재 결과 담당. | +| 16 | pending query 중 restore/reset, 이전 future 늦게 완료 | 이전 generation 값·stale·준비가 새 데이터 화면에 노출되지 않음. | +| 17 | active 또는 early session이 자정/후보 경계를 넘음 | nearest 조회가 활성 run/persisted snapshot을 clear하지 않음. | +| 18 | notification prompt가 열린 상태에서 더 빠른 일정 도착 | prompt 소유권 유지, 해제 후 최신 후보 평가. | +| 19 | active 일정의 실제 삭제/완료 또는 복원 | 도메인 lifecycle이 무효화 처리; active 보호 논리로 누락하지 않음. | +| 20 | repository watch 실패, 기존 ready 있음/없음 각각 | recoverableError와 retry. 동일 generation의 기존 값만 stale 읽기 전용; 처음 오류는 noSchedule 아님. | +| 21 | selected nearest 준비 missing/read exception, 다음 정상 일정 존재 | missing/오류 설명; 더 늦은 일정으로 조용히 우회하거나 empty로 바꾸지 않음. | +| 22 | preparation stream에 해당 ID가 끝내 등장하지 않음 | 일회성 read 결과로 종료, 무기한 `.first` 대기 없음. | +| 23 | 반복 materialize 실패 또는 budget/cap 도달 | 오류 또는 queryLimited 구분. 후보가 있어도 더 이른 미탐색 가능성이 남으면 ready 활성화 안 됨. | +| 24 | 반복 slot 제외·override·frozen 회차와 범위 이동 | 기존 반복 의미 보존, 중복 회차/무한 expansion/불필요한 durable rewrite 없음. | +| 25 | unknown zone/ambiguous 후보와 정상 후보 공존 | 해당 후보의 설명·수정 진입, 정상 후보 선택. 잘못된 zone을 UTC로 조용히 fallback 안 함. | +| 26 | retry 연타·resume+자정+데이터 변경 경합 | 마지막 요청 반영, unbounded 병렬 조회·후속 요청 유실 없음. | +| 27 | ready→error→retry 성공, stale에서 시작 버튼 조작 | stale는 읽기 전용; 성공 검증 후에만 새 start 가능. 기존 활성 세션은 별도. | +| 28 | dispose 후 timer tick, pending query 완료 | 추가 emit/navigation/persist 없음; 모든 소유 timer/subscription 해제. | +| 29 | 단순 clock/zone refresh 전후 DB row/revision과 OS call 기록 비교 | 기존 반복 필요분 외 데이터 변경 없음; start/finish/단계 알림/OS 예약 직접 호출 없음. | +| 30 | actual routed nearest 화면, KO/EN·작은 화면·200% 글자 | empty/error/queryLimited/stale 설명과 재시도/수정 버튼이 잘리지 않고 접근 가능. | +| 31 | Android 실제 foreground→background→resume·시각/zone 변경 | 앱 재설치나 데이터 삭제 없이 새로운 후보와 보존된 세션 확인; 기기·OS·시각·절차 기록. | +| 32 | iOS 실제 foreground→background→resume·시각/zone 변경 | 동일 계약 확인; simulator/mock 결과와 물리 기기 결과를 구분. | + +## 실제 grill 원문 + +아래는 합의 요약을 대화처럼 재구성한 내용이 아니라 전담 agent와 root의 실제 메시지 원문이다. root가 사용자를 대신해 답했다. + +### 질문 1 — 선정 의미 + +> C08 Q1 — grill-with-docs와 CONTEXT/ADR0021/A10을 읽었습니다. 고정 0efdd3ad 코드의 nearest는 처음 now~now+48시간 civil 범위에서 첫 notEnded를 고릅니다. 단순 날짜 refresh만으로는 '가장 가까운' 의미·48시간 밖 공백·약속이 지난 미완료 일정의 고착이 남습니다. 권고: 홈 nearest를 '아직 끝나지 않았고 resolved occurrenceInstantUtc >= 현재 instant인 일정 중 약속 instant가 가장 이른 일정'으로 정의하고 같은 instant는 안정적인 ID 순서로 고릅니다. 제품상 48시간 cutoff는 제거하여 다음 주 한 개뿐이어도 보여주되 반복 expansion은 기존 bounded horizon/cap으로 실행하고 미래 전체를 무한 materialize하지 않습니다. Preparation Start Moment가 이미 지났으나 약속 instant는 미래인 일정은 후보에 남습니다. 현재 활성 Schedule Preparation Session/알림 확인 화면은 별도 소유권으로 유지하여 날짜 갱신이 중단시키지 않습니다. 과거 미진행 notEnded는 nearest 후보에서 빠지되 DB 상태를 자동 완료/삭제하지 않습니다. 이 의미를 채택할까요? 일정 검색의 미확정 tz는 A10처럼 해당 일정만 제외+설명, 정상 후보는 계속 찾는 것을 권합니다. + +질문 1 뒤 코드 근거 보충: + +> C08 추가 코드 근거: ScheduleBloc._onUpcomingReceived는 incoming.scheduleTime < now 또는 null 때 persisted session을 지우므로, 단순 subscription 재시작은 현재 진행 세션까지 끊을 위험이 있습니다. repository.watchSchedulesByDate는 civil filter/sort이고 materialize는 시작 시 한 번입니다. 현재 nearest 테스트는 background week load 실패 1건뿐입니다. 이후 Q2에서 active session 보존·latest generation·갱신 trigger/오류 표시를 구체화하겠습니다. 제품/테스트 파일은 수정하지 않았습니다. + +root 답변 1: + +> Q1 채택합니다. nearest는 현재이상 resolved occurrenceInstantUtc를 가진 notEnded 후보 중 가장이른 instant, 동률 stable ID입니다. 준비시작은 지났어도 약속이 미래면 포함. 48h 제품 cutoff 제거하되 기존 반복 bounded horizon/cap 준수, 현재 materialized/단발 미래 데이터를 조회; 반복 미래 전체를 만들지 않습니다. active session은 별도 소유권 유지, 날짜갱신이 중단/자동완료/삭제하지 않습니다. 과거 미완료는 history/오늘목록에서 상태 보존하되 nearest 제외합니다. invalid/미확정 tz는 A10 원칙으로 해당후보만제외+설명하고 정상후보 계속조회. 추가로 데이터/준비읽기 실패를 빈목록/다음후보로 조용히 바꾸지 말고 empty와 typed recoverable error 구분, retry에원본보존. 이 구분은 다음질문에서 구체화가능. + +### 질문 2 — 갱신과 소유권 + +> C08 Q2 — 갱신과 세션 소유권을 다음으로 확정할까요? 권고: inject한 wall-clock/기기 zone과 lifecycle 신호로 최초 구독, foreground의 device civil 날짜 변경, resume, 수동 시계/zone 변경, 일정/준비 데이터 변경, active session 종료·해제 때 nearest를 재평가합니다. foreground는 다음 경계 one-shot timer + 최대 1분 간격의 가벼운 clock/zone drift 확인(매번 DB 전체 조회 아님)을 사용하고 pause 때 heartbeat를 멈추며 resume은 누락일자별 재생 없이 현재 한 번 평가합니다. 같은 날짜라도 현재 후보의 appointment instant를 지나면 즉시 다음 후보를 골라야 하므로 날짜 변경만 기다리지 않습니다. 새로운 요청은 이전 subscription/타이머를 교체하고 모든 await 뒤 request revision+LocalDataOperationGate generation을 검사하여 이전 준비읽기/이전 DB 응답은 emit·persist·navigation·알림에 반영하지 않습니다. 활성/early-start session 또는 notification prompt는 nearest 변경으로 교체/clear하지 않고 그 소유자가 끝내거나 취소한 뒤 최신 후보를 적용합니다. 화면 표시만 갱신하는 이 경로는 schedule start/finish·OS 알림 예약·단계 알림을 자체 재생하지 않으며 A06/A15 기존 경계를 통합니다. nearest 갱신은 원래 약속 civil/offset·durable revision을 바꾸지 않고, 기존 반복 materialization으로 필요한 미래 회차가 생성되는 경우만 그 기존 트랜잭션 의미를 유지합니다. + +용어 제안 원문: + +> C08 용어 제안(공유 CONTEXT 직접수정금지 준수): 새 도메인용어가 필요하면 `Nearest Upcoming Schedule: The unfinished Schedule whose resolved occurrence instant is the earliest at or after the current instant.` / Avoid `Today's Schedule, active Schedule Preparation Session`로 glossary만 추가할 수 있습니다. 구현세부/타이머정책은 이슈에만 둡니다. reversible query policy라 별도 ADR은 만들 필요 없다고 판단합니다. + +root 답변 2: + +> Q2 권고 채택합니다. 최초/날짜/후보instant경계/resume/clock-zone drift/데이터변경/세션종료 신호로 최신평가, foreground one-shot+최대1분 가벼운 drift확인, pause heartbeat중지 및 resume현재1회. query debounce/coalesce는 허용하지만 마지막요청유실금지, dispose는 모든subscription/timer취소. request revision과gate generation을 await뒤마다확인해이전결과무시, DB교체시기존구독폐기. active/early session과notification prompt 소유권을nearest갱신으로변경/clear하지않고 종료후최신후보. 다만 삭제/완료/복원으로 세션자체가무효해지는것은 기존도메인lifecycle이처리하며 nearest가숨겨서는안됨. 설명대로 이경로는새start/finish/notification재생없고, 단순시간/zonerefresh는durable변경없음. 반복materialization만기존트랜잭션정책유지. 추천최소용어를CONTEXT에추가해야하면문구먼저제안하세요. + +### 질문 3 — 오류와 검색 제한 + +> C08 Q3 — 오류·동시성·bounded 반복검색의 빈 결과를 다음으로 구분할까요? 권고 결과는 loading / ready(schedule) / empty(현재 검색범위에서 적격후보없음) / recoverableError(safe reason, 마지막검증값 있으면 stale 표시)이며 unresolved-zone 후보는 별도 설명/수정 진입을 유지합니다. repository watch/반복 materialize/nearest의 준비 읽기 실패는 empty나 더늦은 후보로 우회하지 않고 retry로 같은 최신평가를 수행합니다. 준비 로드는 `.where(nonNull).first` 무한대기 대신 일회성 로컬 read의 found/missing/error를 사용하고 실제 읽기가 아직 진행중이면 임의 성공/빈값을 만들지 않습니다. stale 카드는 읽기전용이며 새 준비시작/자동화에 쓰지 않고 재검증 후에만 활성화합니다. 처음 로드 실패는 일반 noSchedule 문구와 명확히 다른 KO/EN 오류+재시도, 민감 raw error 금지. 반복 생성은 기존 cap/horizon과 slot/exclusion/frozen 의미 그대로, cap에 닿거나 탐색budget이 소진돼 전 범위 부재가 증명되지 않은 경우 '일정 없음'으로 단정하지 않고 결과 제한/다시시도 상태를 표시합니다. 일반 단발과 materialized 미래는 48h를 넘어서도 선택하며 반복 경계는 범위별 확장/재조회로 최신유효후보만 적용합니다. 이 계약과 기존 Today/달력 UI를 그대로 nearest와 혼동하지 않는 범위를 확정할까요? + +root 답변 3: + +> Q3 권고채택, glossary제안도채택(root즉시반영). loading/ready/empty/recoverableError 및 queryLimited 구분, invalidzone설명은정상후보와함께보여줄수있음. repository/materialize/선정후보준비read실패는후순위후보나empty로바꾸지않음. missing은불변식실패의safe reason+retry/편집복구, 무한nonnull대기금지. 마지막검증값은동일generation만stale읽기전용허용(복원/reset시즉시폐기), fresh재검증전새start/자동화금지. bounded검색은cap/budget때부재증명안되면queryLimited, 이미찾은후보보다더빠른미탐색후보가능성을배제못하면그후보도확정nearest로활성화하지않음. 탐색다시시도는bounded진행/중단가능해야하며무한materialize불가. Today/달력의별도정책은A10유지. 실제3문답전체+보충,구현의존성/검증시나리오를C08.md로동결해주세요. 별도ADR없음. + +## 범위 밖과 완료 판정 + +전체 시간대 정책 재설계, 일정 저장 포맷 일괄 변환, 자동 과거 기록 정리, 무제한 반복 생성, 백그라운드 상시 실행 보장, 새 서버/위치 API는 이 이슈에 포함하지 않는다. 반면 위에 명시한 query 오류 UI·수명주기 연결·준비 읽기 실패 구분·A10 통합·기기 검증은 범위 안이며 별도 이슈 번호가 있다는 이유로 누락할 수 없다. + +문서 작성 단계에서 제품 코드·테스트·QA 도구는 수정하지 않았고 Flutter 테스트나 모바일 기기 검증도 실행하지 않았다. 실제 구현·검증 후 commit/CI/기기 증거를 추가하고 충족하지 못한 항목을 정확히 남긴다. 이 문서 또는 GitHub 이슈 생성 자체는 구현 완료가 아니다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 896ba5a6..84525afa 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -652,16 +652,18 @@ "가장 가까운 일정의 조회 범위를 날짜 변경·resume에 맞춰 갱신한다.", "`get_nearest_upcoming_schedule_use_case.dart:27-34`는 구독 시작의 now와 2일 범위를 고정하고 `app.dart:94-113` resume은 시간 refresh만 한다. clock 주입 후 프로세스가 수일 살아 있는 상황에서 새 날짜 일정이 선택되는지 테스트." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_c08", + "issue_number": 610, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/610", "decisions_file": "plans/audit-2026-09-23/issues/C08.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 23 + "sequence": 23, + "readback_verified": true, + "issue_body_sha256": "28231dd1e26e59c01a8ee52ac151d22ac6a6af05fd3c58f12d518f7cec2da254" }, { "audit_id": "U01", From 074d5fd642a2890dc48933dafcbd038534f759a4 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 10:47:03 +0900 Subject: [PATCH 36/59] fix: connect verified reset operation after screen integration --- .../startup/screens/local_data_recovery_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/startup/screens/local_data_recovery_screen.dart b/lib/presentation/startup/screens/local_data_recovery_screen.dart index dc1a052d..fac95d41 100644 --- a/lib/presentation/startup/screens/local_data_recovery_screen.dart +++ b/lib/presentation/startup/screens/local_data_recovery_screen.dart @@ -34,7 +34,7 @@ class _LocalDataRecoveryScreenState extends State { if (resetResult != null) { return LocalResetProgressScreen( initialResult: resetResult, - retry: widget.reset ?? getIt().reset, + operation: widget.reset ?? getIt().reset, ); } return PopScope( From 204804e32e1c5f741b3d5644698cd3dde3502592 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 10:50:01 +0900 Subject: [PATCH 37/59] docs: specify history deletion and record refreshed screen integration --- CONTEXT.md | 2 +- ...in-schedule-history-until-user-deletion.md | 2 +- handoff/audit-2026-09-23-progress.md | 4 + plans/audit-2026-09-23/README.md | 4 +- .../d03/integrated/d03-merge-targeted-v2.log | 40 ++++ .../d03/integrated/d03-merge-targeted.log | 70 +++++++ .../integrated/d03-merge-ui-capture-v2.log | 6 + .../artifacts/d03/integrated/mypage-entry.png | Bin 0 -> 41490 bytes .../d03/integrated/recovery-busy.png | Bin 0 -> 17160 bytes .../d03/integrated/recovery-confirm.png | Bin 0 -> 49110 bytes .../artifacts/d03/integrated/recovery.png | Bin 0 -> 44473 bytes .../d03-main-integration-validation.json | 19 ++ plans/audit-2026-09-23/issues/D03.md | 5 + plans/audit-2026-09-23/issues/U01.md | 177 ++++++++++++++++++ plans/audit-2026-09-23/state.json | 23 ++- 15 files changed, 339 insertions(+), 13 deletions(-) create mode 100644 plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted-v2.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-ui-capture-v2.log create mode 100644 plans/audit-2026-09-23/artifacts/d03/integrated/mypage-entry.png create mode 100644 plans/audit-2026-09-23/artifacts/d03/integrated/recovery-busy.png create mode 100644 plans/audit-2026-09-23/artifacts/d03/integrated/recovery-confirm.png create mode 100644 plans/audit-2026-09-23/artifacts/d03/integrated/recovery.png create mode 100644 plans/audit-2026-09-23/d03-main-integration-validation.json create mode 100644 plans/audit-2026-09-23/issues/U01.md diff --git a/CONTEXT.md b/CONTEXT.md index f3ff0894..7d0ab6ec 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -430,7 +430,7 @@ _Avoid_: Loaded range, stream range, cached range - A Punctuality Score Reset does not delete Schedules or Schedule Outcomes; Local Data Reset removes the complete score history. - An OnTime Backup preserves the Local Punctuality Score aggregation basis and its latest reset boundary. - **Schedule History** has no age-based or storage-based automatic expiration. -- Deleting a Schedule removes its name, Place, note, Preparation, Schedule Outcome detail, delivery registrations, and appearance in current backup data. +- Deleting a Schedule removes its details and its exclusively owned content; content still owned or referenced by another Schedule, Recurring Schedule, default Preparation, or Preparation template is retained for that independent purpose. The deleted Schedule is absent from later OnTime Backups. Only minimal content-free delivery ownership may remain while cancellation is unresolved, and it is removed after cancellation is confirmed. - After a completed Schedule is deleted, only its non-identifying On Time or Late aggregate contribution may remain for Local Punctuality Score continuity. - Restoring an OnTime Backup may reintroduce a Schedule deleted after that backup's Backup Cutoff, and Restore Preview warns about that replacement effect. - A **Schedule** has one effective **Preparation** for calculating preparation timing. diff --git a/docs/adr/0034-retain-schedule-history-until-user-deletion.md b/docs/adr/0034-retain-schedule-history-until-user-deletion.md index c6c9e08c..6f2066d5 100644 --- a/docs/adr/0034-retain-schedule-history-until-user-deletion.md +++ b/docs/adr/0034-retain-schedule-history-until-user-deletion.md @@ -4,4 +4,4 @@ status: accepted # Retain schedule history until user deletion -Local-only OnTime will not expire Schedule History, Preparation templates, or Schedule Outcome details automatically because age or local storage crosses a threshold. These records remain Durable OnTime Data until the user explicitly deletes them or performs Local Data Reset. Deleting a Schedule will atomically remove its name, Place, note, Preparation relationship and content, detailed Schedule Outcome, and local delivery registrations, and the deleted Schedule will be absent from later OnTime Backups. To preserve the established Local Punctuality Score behavior, an already registered eligible contribution will remain only in non-identifying aggregate On Time or Late counts and will not retain the deleted Schedule identifier or descriptive fields. Restore Preview will explain that replacement with an older Backup Cutoff can reintroduce records deleted after that cutoff. Automatic retention windows and hidden retention of deleted Schedule details were rejected because the former causes unrecoverable offline loss and the latter violates the meaning of explicit deletion. +Local-only OnTime will not expire Schedule History, Preparation templates, or Schedule Outcome details automatically because age or local storage crosses a threshold. These records remain Durable OnTime Data until the user explicitly deletes them or performs Local Data Reset. The selected Schedule and its exclusively owned details are removed in one local-data transaction; shared content remains while independently referenced. Operating-system delivery cancellation is a separate recoverable operation, and unresolved cancellation retains only the minimum content-free ownership needed to finish cleanup, with no claim of completed delivery removal. That ownership must be durably preserved before the transaction can remove its source identifiers. The deleted Schedule will be absent from later OnTime Backups. To preserve the established Local Punctuality Score behavior, an already registered eligible contribution will remain only in non-identifying aggregate On Time or Late counts and will not retain the deleted Schedule identifier or descriptive fields. Restore Preview will explain that replacement with an older Backup Cutoff can reintroduce records deleted after that cutoff. Automatic retention windows and hidden retention of deleted Schedule details were rejected because the former causes unrecoverable offline loss and the latter violates the meaning of explicit deletion. diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 6e30bfa8..3b3b99e7 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -85,3 +85,7 @@ A10 원래 전담도 재개되어 실제 Q1~Q4/root 답변 완료. civil/instant ## 2026-09-24 D03 최종 로컬 검증 D03 원래 전담의 P2 구현과 root 연결이 완료됐다. 전체872/집중71,87.99%,analyze/generated/local-only 및 native SDK compile 통과.579파일 재해시에서제품/test소스 변경없음, QA 대역에 명시적unknown설정만추가 후 hostSIGKILL11 모두통과. d03-final-validation.json 및artifacts/d03/final 참조. 원격CI와실기기검증별도. U02#609 게시/readback23/64,41개생성전. C08 전담grill진행, glossary root반영. 다음구현A06#598. + +## 최신 main 통합 및 다음 작업 + +main c7184b2a Figma PR607 충돌을659a5fc4에서해결,복구operation인자오류074d5fd6수정. 관련36tests+실제UIcapture2통과,root3화면직접확인. 원격074d5fd6 CI확인중. D03상세이슈최신본문은아직게시대기. C08#610/U01#611 게시검증하여25/64,39개생성전. U01CONTEXT/ADR0034 독점/공유원문삭제정책root반영. A06전담구현agent가Flutter소유권가지고focused/analyze/full준비중,root제품파일수정금지. U03전담grill문답중. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index a204f3b8..61bbf579 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -43,7 +43,9 @@ | C08 | [#610](https://github.com/DevKor-github/OnTime-front/issues/610) | 실제3문답·현재 instant/세션보존/수명주기 갱신·오류/제한 구분,32개 검증 시나리오 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **24/64개**를 게시하고 원격 본문 동일성을 확인했으며 **40개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 후 최종872 tests/87.99% 및 host kill11 검증을 통과했고 CI·기기 확인을 진행한다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +| U01 | [#611](https://github.com/DevKor-github/OnTime-front/issues/611) | 실제3문답·이력 단일삭제/공유원문·점수보존/취소ownership,32개 검증 시나리오 확정 | + +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **25/64개**를 게시하고 원격 본문 동일성을 확인했으며 **39개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 후 최종872 tests/87.99% 및 host kill11 검증을 통과했고 CI·기기 확인을 진행한다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted-v2.log b/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted-v2.log new file mode 100644 index 00000000..de33b519 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted-v2.log @@ -0,0 +1,40 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: (setUpAll) +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: education setting choice rereads state without claiming a grant +00:01 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:01 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:01 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:01 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:01 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:01 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:01 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:01 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:02 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:02 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:02 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: local data and bundled privacy destinations remain in-app +00:02 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: scope defaults to only this occurrence and supports following scope +00:02 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:02 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: failed reset remains recoverable and never reports success +00:02 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: partial reset opens recovery instead of reporting completion +00:02 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: privacy keeps all six policy sections scrollable +00:02 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: management detail and end confirmation keep independent preparation +00:02 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: (tearDownAll) +00:02 +36: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log b/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log new file mode 100644 index 00000000..76e903d4 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log @@ -0,0 +1,70 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart +lib/presentation/startup/screens/local_data_recovery_screen.dart:37:9: Error: No named parameter with the name 'retry'. + retry: widget.reset ?? getIt().reset, + ^^^^^ +lib/presentation/startup/screens/local_reset_progress_screen.dart:9:9: Context: Found this candidate, but the arguments don't match. + const LocalResetProgressScreen({ + ^^^^^^^^^^^^^^^^^^^^^^^^ +00:00 +0 -1: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart [E] + Failed to load "/Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart": + Compilation failed for testPath=/Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: lib/presentation/startup/screens/local_data_recovery_screen.dart:37:9: Error: No named parameter with the name 'retry'. + retry: widget.reset ?? getIt().reset, + ^^^^^ + lib/presentation/startup/screens/local_reset_progress_screen.dart:9:9: Context: Found this candidate, but the arguments don't match. + const LocalResetProgressScreen({ + ^^^^^^^^^^^^^^^^^^^^^^^^ + . +00:00 +0 -2: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart [E] + Error: The Dart compiler exited unexpectedly. + package:flutter_tools/src/base/common.dart 34:3 throwToolExit + package:flutter_tools/src/compile.dart 1009:11 DefaultResidentCompiler._compile. + dart:async/zone_root.dart 48:47 _rootRunUnary + dart:async/zone.dart 733:19 _CustomZone.runUnary + dart:async/future_impl.dart 948:45 Future._propagateToListeners.handleValueCallback + dart:async/future_impl.dart 977:13 Future._propagateToListeners + dart:async/future_impl.dart 862:9 Future._propagateToListeners + dart:async/future_impl.dart 720:5 Future._completeWithValue + dart:async/future_impl.dart 804:7 Future._asyncCompleteWithValue. + dart:async/zone_root.dart 35:13 _rootRun + dart:async/zone.dart 726:19 _CustomZone.run + dart:async/zone.dart 625:7 _CustomZone.runGuarded + dart:async/zone.dart 666:23 _CustomZone.bindCallbackGuarded. + dart:async/schedule_microtask.dart 40:35 _microtaskLoop + dart:async/schedule_microtask.dart 49:5 _startMicrotaskLoop + dart:isolate-patch/isolate_patch.dart 127:13 _runPendingImmediateCallback + dart:isolate-patch/isolate_patch.dart 193:5 _RawReceivePort._handleMessage + +00:00 +0 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: (setUpAll) +00:00 +0 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:00 +1 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:00 +2 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:00 +3 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:00 +4 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +5 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +6 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +7 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +8 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +9 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:01 +10 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: timing education is optional once and settings require explicit choice +00:01 +11 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: timing education is optional once and settings require explicit choice +00:01 +12 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:01 +13 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:01 +14 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:01 +15 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:01 +16 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:01 +17 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:01 +18 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:01 +19 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:01 +20 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: new notification grant initializes local notifications +00:01 +21 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI bootstrap failure can retry without showing private exception text +00:01 +22 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: restricted notification state can open system settings +00:01 +23 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: restricted notification state can open system settings +00:01 +24 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: ko partial deletion screen is distinct from complete and fits large text +00:01 +25 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: local data and bundled privacy destinations remain in-app +00:01 +26 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: local data and bundled privacy destinations remain in-app +00:01 +27 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: (tearDownAll) +00:01 +27 -2: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart diff --git a/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-ui-capture-v2.log b/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-ui-capture-v2.log new file mode 100644 index 00000000..475f0448 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-ui-capture-v2.log @@ -0,0 +1,6 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: (setUpAll) +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: (tearDownAll) +00:00 +2: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/d03/integrated/mypage-entry.png b/plans/audit-2026-09-23/artifacts/d03/integrated/mypage-entry.png new file mode 100644 index 0000000000000000000000000000000000000000..2c8a61e334eb32d0fd891ff2c23c5eb30e106b03 GIT binary patch literal 41490 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

ns1SEPEpo%>v;JTSvGPI%z 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 ezvWB*DE}RYeYJBmksob=QdiZ!T6pEoEO>6{pw3 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

K<-*4LYAMX;untCQD(`U&% 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 a$TN!dU@SMW+YAZ literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/d03/integrated/recovery.png b/plans/audit-2026-09-23/artifacts/d03/integrated/recovery.png new file mode 100644 index 0000000000000000000000000000000000000000..26d09a136df9e2baaa6fb5a875c7f04e18d4d4d8 GIT binary patch literal 44473 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 C{BoNB literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/d03-main-integration-validation.json b/plans/audit-2026-09-23/d03-main-integration-validation.json new file mode 100644 index 00000000..cdfca438 --- /dev/null +++ b/plans/audit-2026-09-23/d03-main-integration-validation.json @@ -0,0 +1,19 @@ +{ + "merge_commit": "659a5fc485efe58da76145e6ead2655f8664f766", + "fix_commit": "074d5fd642a2890dc48933dafcbd038534f759a4", + "upstream_main": "c7184b2ae477e6a24a47fd0b9da79d3ed52b8cf4", + "targeted_tests": 36, + "capture_tests": 2, + "scope": "my page, recurring form, refreshed screens, startup recovery; local tree also contains uncommitted A06 dependencies, remote CI must verify committed source separately", + "initial_failure": "incorrect retry named argument introduced by merge; corrected to operation then36pass", + "visual_review": "root viewed actual widget captures: mypage, recovery, recovery-busy; no clipped content in these captured viewports", + "artifacts": { + "plans/audit-2026-09-23/artifacts/d03/integrated/recovery.png": "16e66a342fd6a330241b2cd0e191bda2c11082293fbea0a5b8e649fda1d31294", + "plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-ui-capture-v2.log": "af5babf3ccb0d3d28014589f1a1573bd6d4babfdbb3d1fe5a7a13eddb97709f0", + "plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log": "c5311115364928fd5e275c709775f218183424cac4e097cf094572e34302d274", + "plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted-v2.log": "5a4855e44988d76ca7cc60f2bce2adf5ea533e9a7d2d27315b129997ffc9fb14", + "plans/audit-2026-09-23/artifacts/d03/integrated/mypage-entry.png": "74229da8a4c532f5da307d98b40776041970d13ad13201bb9791b8d0c3c9a5f5", + "plans/audit-2026-09-23/artifacts/d03/integrated/recovery-confirm.png": "6dc0c6ea3fab24b5857f45353d10976590fcf9114d2bf82c10d22ef3a6cda073", + "plans/audit-2026-09-23/artifacts/d03/integrated/recovery-busy.png": "cb4615d5f87fa6bdd14a61d83b05bd9aab822c1214c53d5aec0f7a2864c8319d" + } +} diff --git a/plans/audit-2026-09-23/issues/D03.md b/plans/audit-2026-09-23/issues/D03.md index 19bc2472..8e6bde71 100644 --- a/plans/audit-2026-09-23/issues/D03.md +++ b/plans/audit-2026-09-23/issues/D03.md @@ -267,3 +267,8 @@ restore는 journal을 새 DB의 일부로 교체하지 않는다. 구세대 취 원격 CI·실제 Android/iOS 전달/중단 복구/백업 제외 확인·병합·출시는 별도 단계다. 모바일 기기 수용조건이 남아 이슈는 OPEN을 유지한다. D01의 일반 손상 DB 복구 및 D02 후보 검증은 후속 범위이며, 이번에는 D03에 필요한 DB/DI 이전 복구 화면을 연결했다. 구현 커밋: `5f3adf11aaaa3e36d6e4e25c41be6c06e4105480` — 제품/테스트/QA 49개 파일. 생성 산출물은 포함하지 않았다. + + +### 최신 main 화면 통합 + +upstream `c7184b2a`의 Figma 화면 PR #607과 충돌해 D03 푸시 직후에는 PR CI가 생성되지 않았다. `659a5fc4`에서 디자인·실제 알림 상태·typed 초기화 결과를 함께 보존해 충돌을 해결했다. 초기 통합에서 복구 화면 인자명이 잘못 연결된 컴파일 실패를 발견했고 `074d5fd6`에서 `operation`으로 수정했다. 그 뒤 관련 화면/복구 **36개 테스트**와 실제 글꼴 캡처 **2개 테스트**를 통과했다. partial reset이 완료 callback을 부르지 않는 회귀도 추가했다. 원래 디자인 artifact는 유지하고 통합 화면 캡처를 `artifacts/d03/integrated/`에 별도로 보존했다. root가 마이페이지·복구·복구 대기 화면을 직접 확인했다. 로컬 실행 시 독립 A06 WIP가 존재했으므로 정확한 committed tree 검증은 원격 CI와 구별한다. diff --git a/plans/audit-2026-09-23/issues/U01.md b/plans/audit-2026-09-23/issues/U01.md new file mode 100644 index 00000000..7b73da7b --- /dev/null +++ b/plans/audit-2026-09-23/issues/U01.md @@ -0,0 +1,177 @@ +# [P1][U01] 완료·비정상 일정 이력을 개별 삭제하고 원문·공유 자원·점수·알림 정리를 일관되게 보장 + +## 상태와 목적 + +- 감사 항목: U01 / 우선순위 P1 / 사용자 승인된 전체 64개 개선 범위. +- 전담 서브에이전트: `/root/grill_u01`. `grill-with-docs`를 직접 읽고 적용했으며 아래 실제 3개 질문에 root가 사용자의 대리로 답했다. +- 상태: 정책·문서 확정, 구현과 실행 검증 전. 이 문서 작성 중 제품 코드·테스트·GitHub를 수정하거나 Flutter 검증을 실행하지 않았다. +- 조사 기준: `af28e8c8fd14d3fe9badff0667f032a372c1641c`의 커밋 내용. D03 제품 기반은 `5f3adf11`; 동시에 진행 중인 A06 작업 내용을 이 이슈의 검증된 구현으로 취급하지 않는다. +- 결과 목표: 사용자가 완료·지각·비정상·과거 미완료 이력을 명시적으로 삭제할 수 있어야 한다. 선택한 이력의 원문은 제거하고, 다른 일정·반복 규칙·공유 준비 및 이미 등록된 점수 집계는 보존한다. 로컬 데이터 삭제와 OS 알림 정리의 결과는 구분한다. + +## 확인한 결함과 고정 근거 + +다음 링크는 모두 조사 기준 커밋에 고정되어 있다. 코드 관찰은 실행 재현 결과와 구분한다. + +| 근거 | 현재 동작과 영향 | +|---|---| +| [ScheduleDetail:105–109](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/presentation/calendar/component/schedule_detail.dart#L105) | 편집은 미래 미종료·준비 전 조건이지만 삭제는 `notEnded`만 허용한다. 정상/지각/비정상 완료 이력의 삭제 액션이 사라진다. | +| [CalendarScreen:161](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/presentation/calendar/screens/calendar_screen.dart#L161), [분기:451](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/presentation/calendar/screens/calendar_screen.dart#L451) | 반복이면 범위 선택 후 별도 use case로 삭제한다. 이력에도 이 경로를 그대로 열면 ‘이후’ 범위와 실제 과거 보호 동작이 충돌할 수 있다. | +| [반복 보호:130](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/data/repositories/recurring_schedule_repository_impl.dart#L130), [delete:1131](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/data/repositories/recurring_schedule_repository_impl.dart#L1131) | occurrence 삭제는 `isStarted`를 거부하고 exclusion을 기록한다. following은 시작·frozen·완료·과거 회차를 보호하여 선택된 완료 이력을 남길 수 있다. | +| [ScheduleDao:35](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/data/daos/schedule_dao.dart#L35), [준비 FK:15](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/data/tables/preparation_schedule_table.dart#L15), [FK 활성화:81](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/core/database/database.dart#L81) | 일회성 DAO는 schedules 행만 삭제한다. 개별 준비 단계에 cascade가 없는 참조가 있어 원문 정리 누락 또는 FK 실패를 확인해야 한다. Place도 이 경로에서 삭제하지 않는다. | +| [반복 _removeRow:1118](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/data/repositories/recurring_schedule_repository_impl.dart#L1118) | 연결된 준비 단계를 unlink/delete하고 일정 행을 제거하지만 고아 Place나 더 이상 참조하지 않는 개별 preparation definition의 정리는 없다. | +| [ScheduleRepository:79](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/data/repositories/schedule_repository_impl.dart#L79) | 일정 삭제·runtime 정리·revision 증가가 분리되어 있고 recurring 경로는 내부 `_changed()` 후 외부 revision을 다시 증가시킬 수 있다. | +| [완료와 점수:136](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/data/repositories/schedule_repository_impl.dart#L136), [기존 점수 회귀](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/test/data/repositories/local_schedule_score_test.dart) | 완료 점수는 transaction에서 한 번 기록하고, 삭제 후 이미 등록된 집계를 유지하는 테스트가 있다. U01은 이 규칙을 바꾸지 않는다. | +| [Backup snapshot:255](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/core/backup/backup_service.dart#L255), [모든 definition 캡처:23](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/core/backup/recurring_backup_data.dart#L23) | 백업은 살아 있는 schedule의 개별 준비와 별도로 recurring definitions 전체를 캡처한다. UI 목록에서 사라졌다는 사실만으로 삭제 원문이 새 백업에서 사라졌다고 볼 수 없다. | +| [mutation effects:22](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart#L22), [cleanup report:203](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/core/services/alarm_registration_cleanup.dart#L203) | DB 성공 후 알림 취소 오류는 관찰되지만 삭제 호출 결과로 구분해서 전달되지 않는다. 공통 owner의 typed cleanup report와 content-free ownership을 재사용할 수 있다. | +| [MonthlySchedulesBloc:138](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/presentation/calendar/bloc/monthly_schedules_bloc.dart#L138) | 삭제 전 preparation 표시를 낙관적으로 변경하고 실패 시 되돌린다. DB 실패와 commit 후 OS 정리 미완료를 같은 실패로 취급하지 않도록 결과를 분리해야 한다. | +| [현재 영문 확인 문구:481](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/lib/l10n/app_en.arb#L481) | ‘cannot be restored’는 앱 내 undo 부재와 과거 외부 백업 복원의 차이를 설명하지 않는다. | + +도메인 근거는 [CONTEXT](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/CONTEXT.md#L425), [ADR0025 점수](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/docs/adr/0025-calculate-punctuality-score-locally.md), [ADR0034 이력 보존·삭제](https://github.com/DevKor-github/OnTime-front/blob/af28e8c8fd14d3fe9badff0667f032a372c1641c/docs/adr/0034-retain-schedule-history-until-user-deletion.md)다. 문서의 ‘원자적 삭제’는 DB와 OS를 한 transaction으로 묶는 보장을 뜻하도록 유지하면 안 된다. 아래 확정 정책에 따라 root가 기존 문구를 정합화한다. + +## 확정 동작 + +### 1. 삭제 가능한 대상과 범위 + +1. 정상 완료, 지각 완료, 비정상 완료, 준비 중이 아닌 과거 미완료 일정에 개별 삭제를 제공한다. 날짜가 오래됐다는 이유로 자동 삭제하지 않는다. +2. 반복 이력에서 삭제 대상은 **이 회차만**이다. 다른 과거/미래 회차, 반복 규칙, 공용 준비를 보존하고 해당 slot의 재생성만 방지한다. 이력 진입점에는 ‘이 회차와 이후’를 제공하지 않는다. +3. 미래 미진행 회차의 기존 범위 선택은 보존한다. 종료 이력의 일반 편집을 허용하는 변경은 아니다. +4. 실제 유효한 Preparation Run/Early Start를 포함한 준비 중 일정은 일회성/반복 모두 삭제를 차단한다. 준비 화면 복귀 또는 먼저 종료 안내를 제공한다. **일회성 started 삭제를 허용하던 기존 동작과 테스트를 의도적으로 변경한다.** +5. stale `isStarted`와 실제 유효 run 불일치는 고아 상태 검증/복구 문제다. 임의 완료 처리나 강제 삭제로 해소하지 않는다. 삭제 eligibility는 UI 추정만이 아니라 실제 실행 경계에서 검사한다. +6. 확인 화면은 선택 이력 식별, 원문 영구 삭제, 다른 일정 보존, 기존 점수 집계 유지, 앱 내 undo 없음, 외부 과거 백업 복원 시 재등장 가능성을 정확하게 설명한다. 취소하면 DB·revision·runtime·OS 요청이 바뀌지 않는다. + +### 2. 데이터 삭제 transaction과 공유 원문 + +1. 실제 참조 그래프를 읽어 선택 Schedule, outcome 상세, schedule 고유 이름·메모·준비 및 관계를 하나의 로컬 데이터 transaction으로 제거한다. 연결 리스트 FK는 안전한 순서로 해제한다. +2. Place/Preparation definition은 다른 Schedule, Recurring Segment, Default Preparation, 사용자 템플릿 등 살아 있는 소유/참조가 없는 **해당 삭제로 영향받는 데이터만** 정리한다. 공유 판단은 ID 참조 기준이며 이름 일치로 추정하지 않는다. +3. 공유 콘텐츠는 다른 기능의 독립된 소유 데이터로 보존한다. 반복 segment 자체의 공통 이름/장소/준비가 남는 것은 선택 회차 상세를 몰래 보관하는 것과 구분한다. 선택 회차 override 등 독점 원문은 남기지 않는다. +4. 관련 없는 기존 고아 데이터를 전역 청소하지 않는다. 다른 회차·템플릿·기본 준비를 삭제하지 않는다. +5. 이미 등록된 On Time/Late aggregate, 점수 reset boundary, 다른 결과는 불변이다. Abnormal outcome은 점수 기여를 새로 만들지 않는다. 삭제된 일정 ID나 상세가 점수 명목으로 남아서는 안 된다. +6. 성공한 의미 있는 삭제는 durable revision을 정확히 1회 갱신한다. 동일한 삭제 의도의 이미 완료된 재시도는 no-op이며 revision 0회다. DB 단계 실패는 전체 rollback으로 기존 내용·점수·revision을 보존한다. +7. 반복 재생성 방지에는 도메인에 필요한 segment/slot/ordinal 최소 키만 남긴다. 삭제 일정 ID·이름·메모·개별 준비·결과 원문은 exclusion에 보관하지 않는다. 기존 count/ordinal/slot 소비 규칙을 유지한다. +8. 새 Backup Cutoff 이후 만들어진 백업에는 삭제한 개별 원문이 없다. 독립적으로 공유되는 정의와 점수 집계는 남는다. 이전에 내보낸 외부 파일은 변경하지 않는다. 명시적인 오래된 백업 복원은 삭제 전 이력을 재도입할 수 있다. + +### 3. stale 확인·멱등성과 알림 정리 + +1. 확인된 삭제 의도는 대상 identity, 확인 당시 내용/상태 기준, data generation을 가진다. commit transaction에서 최신 기준을 다시 검사한다. +2. 확인 뒤 대상 수정·시작·완료·restore/reset·같은 ID 재생성이 있으면 typed stale/conflict로 거절하고 재조회 및 새 확인을 요구한다. 무관한 일정의 전역 revision 변화만으로 막지 않는다. 같은 ID라는 이유만으로 이전 삭제 의도를 새 객체에 적용하지 않는다. +3. 실제 OS 취소 식별자를 잃기 전에 A15/D03 공통 owner 아래 필요한 content-free ownership이 durable하게 보존됐음을 확인한다. journal 쓰기 실패 때문에 필요한 식별자 보존을 확인하지 못하면 DB 삭제를 commit하지 않고 안전한 오류를 반환한다. 단순 메모리 보존을 프로세스 사망 후 복구 보장으로 표시하지 않는다. +4. durable ownership이 보존된 이후 OS 취소 실패/unknown이면 DB 삭제 후 부분 결과가 가능하다. OS 작업을 DB transaction으로 옮기거나 둘이 원자적이라고 주장하지 않는다. 기존 pending actual provider ID와 최소 schedule ID 외에 원문·payload·fingerprint를 남기지 않는다. +5. commit 후에는 삭제 행을 재생성하지 않는다. 목록에서 지워진 사실을 유지하며 ‘이력 삭제됨, 알림 정리 확인 필요’와 명시적 재시도를 제공한다. 모든 표적 취소 확인 뒤 최소 소유권도 제거한다. +6. journal/registry 실패, unknown ownership, 응답 없는 provider를 완료로 표시하지 않는다. timeout은 사용자에게 지연을 알리는 수단이며 owner 해제나 OS 성공 근거가 아니다. 무차별 cancelAll 또는 동일 ID의 새 예약 중첩을 만들지 않는다. +7. 부분 결과 재시도는 현재 generation의 같은 정리 의도를 처리한다. DB 재삭제·revision 증가 없이 공통 owner의 cleanup을 재시도한다. old generation의 완료 callback은 새 DB/UI/registry에 영향을 주지 않는다. +8. runtime/Early Start의 해당 일정 상태는 공통 무효화/정리 규칙에 따라 제거한다. 오래된 callback이 삭제 이력을 다시 보여 주거나 다른 일정의 run을 지우지 않도록 검증한다. + +### 4. 화면·접근성·오류 + +- swipe 삭제 외에 스크린리더·키보드로 접근할 수 있는 명시적 삭제 액션 또는 동등한 semantics action을 제공한다. 동작 이름과 대상이 읽혀야 한다. +- 한국어/영어 확인·성공·부분 결과·실패·준비 중 안내를 구분한다. 200% 텍스트와 작은 화면에서 버튼과 핵심 설명이 접근 가능해야 한다. +- 빠른 연타, 여러 액션 경로, 늦은 callback은 한 확인 의도에 대해 single-flight로 처리한다. 새 generation의 화면은 오래된 완료로 닫히거나 바뀌지 않는다. +- DB 실패는 기존 이력이 유지된 상태로 오류를 알리고 재시도할 수 있다. commit 후 OS 부분 결과를 ‘이력 삭제 실패’로 표시하거나 이력을 되살리지 않는다. +- 예외 원문, 내부 경로, DB/OS 구현 문자열을 사용자에게 노출하지 않는다. 장기 보관함/휴지통/undo/자동 retention을 추가하지 않는다. + +## 구현 순서와 의존성 + +1. 삭제 eligibility와 확인 대상 identity/generation의 계약 및 typed 결과를 domain/use case에 정의한다. 기존 future 삭제·반복 following 정책과 이력 단일 삭제를 구분한다. +2. DAO/repository 참조 그래프와 FK를 실제 데이터로 검증하고 atomic 삭제, 공유 참조 보존, 점수 불변, 정확한 revision, 멱등을 구현한다. +3. A15/D03 owner의 durable ownership 확인 → DB commit → 취소/부분 결과/명시 재시도를 연결한다. 같은 기능의 두 번째 queue 또는 전역 cancelAll 경로를 만들지 않는다. +4. 일회성/반복 UI를 동일한 삭제 결과 계약으로 연결하고 스크린리더 접근, KO/EN 문구, 처리 중 중복 방지를 추가한다. +5. 실제 DAO/repository+use case+widget 통합을 검증한 뒤 Android/iOS 시나리오와 백업 read-back으로 마무리한다. + +- [A08 #601](https://github.com/DevKor-github/OnTime-front/issues/601): aggregate identity/transaction/stale와 durable revision 계약 재사용. 개별 삭제를 별도 무제한 mutation ledger 도입 명분으로 쓰지 않는다. +- [A09 #602](https://github.com/DevKor-github/OnTime-front/issues/602), [D02 #604](https://github.com/DevKor-github/OnTime-front/issues/604): backup/restore generation과 검증된 교체 경계. U01이 복원 구현 전체를 다시 만들지 않는다. +- [A15 #594](https://github.com/DevKor-github/OnTime-front/issues/594), [D03 #595](https://github.com/DevKor-github/OnTime-front/issues/595): OS 효과 직렬화, actual provider ownership, 취소 미완료 증거, 프로세스 사망 후 복구. 코드/CI 통과와 실제 전달/취소 증거를 구분한다. +- [A06 #598](https://github.com/DevKor-github/OnTime-front/issues/598): 실제 준비 run 유효성 및 callback의 세대 검사와 충돌하지 않도록 연계한다. +- [D04 #605](https://github.com/DevKor-github/OnTime-front/issues/605), [D05 #606](https://github.com/DevKor-github/OnTime-front/issues/606): 필요 시 schema migration/backup 호환성. 기본 계획은 schema 2 및 backup v2/v1 호환을 유지한다. 새로운 durable identity 필드가 실제 필요하면 호환·migration 근거 없이 추가하지 않는다. +- F02의 전체 이력 검색/필터, C02의 전체 조회 성능, 완전한 고아 데이터 정비는 별도 범위다. U01은 삭제 대상이 실제 사용 가능한 현재 캘린더 이력 화면을 중심으로 완결한다. + +## 검증 시나리오 + +아래는 계획이며 아직 실행 결과가 아니다. 단순 mock 메서드 호출 확인 대신 실제 DB 행·참조·집계·revision과 최종 UI를 확인한다. + +| ID | 조건/행동 | 필수 기대 결과 | +|---|---|---| +| U01-01 | 정상 완료 이력 삭제 확인 | 해당 원문 제거, 다른 일정 유지, aggregate/reset boundary 불변. | +| U01-02 | 지각 완료 이력 삭제 | 늦은 결과 상세 제거, 등록된 집계 감소 없음. | +| U01-03 | 비정상 완료 이력 삭제 | 결과 제거, 점수 기여 생성/감소 없음. | +| U01-04 | 준비 중이 아닌 과거 미완료 삭제 | 삭제 가능, 임의 정상/지각/비정상 완료 기록 없음. | +| U01-05 | 반복 완료·과거 미완료 삭제 | ‘이 회차만’ 확인, following 미노출, 다른 회차/규칙 불변. | +| U01-06 | 삭제 반복 회차의 월 재조회·materialize·재시작 | exclusion에 의해 재생성 없음, slot/ordinal/count 의미 유지. | +| U01-07 | 미래 미진행 반복 삭제 | 기존 occurrence/following 선택 유지, 보호된 이력 보존. | +| U01-08 | 일회성/반복 유효 run 또는 Early Start 중 삭제 | 삭제 차단 및 준비 화면/종료 안내, 데이터·OS 변화 없음. | +| U01-09 | stale isStarted 및 runtime 불일치 | 안전한 검증/복구 안내, 임의 완료·강제삭제 없음. | +| U01-10 | 삭제 확인 취소·back·dismiss | DB/score/revision/runtime/OS mutation 0. | +| U01-11 | FK 활성 DB에서 여러 연결 준비 단계 포함 삭제 | FK 오류 없이 전부 정리; 독점 Place/definition 제거. | +| U01-12 | 두 일정이 같은 Place ID 참조 | 선택 일정만 제거, 공유 Place와 다른 일정 유지. | +| U01-13 | recurring segment/다른 회차가 definition 참조 | 정의와 공통 준비 유지, 선택 회차 override만 제거. | +| U01-14 | 삭제 대상과 이름이 같지만 다른 ID인 준비/장소 | 이름으로 공유/삭제 판단하지 않음, 무관 행 불변. | +| U01-15 | 영향받지 않은 기존 orphan 데이터 존재 | 전역 청소하지 않음. | +| U01-16 | unlink/delete/exclusion/revision 중간 실패 주입 | 전체 rollback, 이력/점수/revision/참조 원상 유지. | +| U01-17 | 한 삭제의 연타·동시 호출·이미 완료 재시도 | 유효 삭제 commit 1회/revision 1회, no-op 추가 0회. | +| U01-18 | 확인 후 같은 대상 수정/시작/완료 | 최신 상태 재검사하여 stale/conflict, 새 확인 필요. | +| U01-19 | 확인 후 unrelated 일정 수정 | 전역 revision만 이유로 삭제 차단하지 않음. | +| U01-20 | 확인 후 restore/reset 또는 같은 ID 새 identity | 옛 의도로 새 객체 삭제 금지, old callback 영향 없음. | +| U01-21 | 삭제로 사라질 actual provider ID의 journal 쓰기 실패 | 필요한 durable 보존이 확인되지 않으면 DB commit 없음. | +| U01-22 | durable 소유권 보존 후 OS 취소 성공 | DB 원문 부재, 모든 표적 확인 후 해당 ownership 제거. | +| U01-23 | durable 보존 후 native/fallback 취소 실패·unknown | 이력은 삭제된 채 partial 표시, 원문 없는 최소 증거 보존. | +| U01-24 | provider Future가 반환하지 않음 | 지연 안내 가능, owner 유지, 성공/timeout release/무차별 cancelAll 없음. | +| U01-25 | partial 후 명시 재시도 및 재시작 복구 | cleanup만 재시도, DB 복원/재삭제/추가 revision 없음. | +| U01-26 | DB commit 직후 프로세스 종료 | 새 시작 시 삭제 사실 유지, durable ownership으로 미정리 알림 복구; 실제 host/device 증거 구분. | +| U01-27 | score reset 전후 삭제 및 이미 기여한 정상/지각 혼합 | aggregate와 최신 reset boundary 불변, deleted ID 점수 상세 없음. | +| U01-28 | 삭제 이후 백업 export와 decode/read-back | 삭제 고유 문자열/ID/준비 상세 부재; 공유 정의/집계/최소 exclusion만 유지. | +| U01-29 | 삭제 전 외부 백업 파일 및 명시 복원 | 파일 무변경, 복원 시 이력 재등장 가능 문구 및 replacement 동작 일치. | +| U01-30 | KO/EN, 200% text, 작은 화면, 화면읽기/키보드 | 확인/취소/부분 결과/재시도 접근 가능, swipe에만 의존하지 않음. | +| U01-31 | DB 오류와 OS partial 각각 발생 | 전자는 기존 이력 유지, 후자는 이력 제거 유지; 서로 다른 정확한 문구. | +| U01-32 | Android/iOS 실제 일정·이력·알림으로 삭제 | 앱 목록·재시작·알림 목록 관측과 실제 OS 취소를 기록; fake 호출 성공만으로 기기 완료라 하지 않음. | + +## 완료 수용조건 + +- [ ] 종료 결과 전체 및 과거 미완료의 단일 삭제가 실제 UI에서 접근 가능하다. +- [ ] 일회성 started 삭제 허용의 의도된 동작 변경과 run/early/고아 상태 경계가 검증됐다. +- [ ] 반복 이력 삭제가 선택 회차만 제거하고 재생성·다른 이력 손실을 만들지 않는다. +- [ ] 실제 FK 활성 DB에서 독점 원문 삭제·공유 참조 보존·rollback·집계 불변·정확한 revision을 증명했다. +- [ ] stale identity/generation·동시 호출·no-op 재시도와 새 동일ID 객체를 구분한다. +- [ ] OS 식별자 durable 보존 전 DB 삭제를 commit하지 않으며 공통 owner를 통한 partial/retry/재시작 복구를 증명했다. +- [ ] 삭제 이후 백업 원문 부재와 이전 외부 백업 불변을 read-back했다. +- [ ] 한국어/영어·200%·접근성·실패/부분 결과 화면 검증 근거를 남겼다. +- [ ] 적절한 formatter/analyzer/관련 및 전체 회귀, Android/iOS 실제 검증 결과를 커밋 SHA와 함께 기록했다. +- [ ] 열린 기기 조건이나 미실행 조건을 통과로 표시하지 않았고, issue/PR/CI/기기검증/병합/출시 상태를 구분했다. + +## 실제 grill-with-docs 전체 문답과 보충 + +아래 질문·답변은 실제 주고받은 문장 그대로다. 권고안은 답변으로 채택된 범위에서만 최종 계약이다. + +### Q1 — 이력 삭제 범위와 실제 준비 중 차단 + +**전담 서브에이전트 질문** + +> U01 grill-with-docs 읽고 현재 코드 확인했습니다. Q1: 완료·지각·비정상 및 과거 미완료 이력의 삭제를 열 때, 반복 일정의 완료/과거 회차에서도 ‘이 회차와 이후’ 범위를 허용할까요? 권고: U01 이력에서는 ‘이 회차만 삭제’를 명확히 확인하고, 반복 규칙·다른 완료 이력·미래 회차는 유지하며 재생성 방지 제외만 남깁니다. 미래 미진행 회차의 기존 범위 선택은 유지합니다. 실제 준비 중(isStarted 또는 유효 Early Start/Run)인 일정은 일회성/반복 모두 ‘준비를 먼저 종료’로 차단하고, 종료 이력 편집은 계속 막습니다. 근거: UI canDelete는 notEnded만 허용하지만 반복 repo occurrence 삭제는 종료 상태와 무관, following은 과거/완료를 보호하여 선택 이력이 안 지워지는 모순이 있습니다. 일회성 현재 테스트는 started 삭제 허용하므로 차단을 채택하면 의도적으로 수정해야 합니다. 이 범위를 채택할까요? + +**root 대리 답변** + +> Q1 채택: 완료/지각/비정상 및 과거미완료 이력은단일삭제, 반복은이회차만+재생성방지exclusion. 다른과거/미래회차/규칙보존, 이력진입점에서following제공하지않음. 미래미진행의기존범위선택유지, 완료이력편집은열지않음. 실제유효준비중(run/early포함)은일회성/반복모두삭제차단+준비화면복귀/먼저종료안내, 기존started삭제허용테스트는새정책에맞춰수정대상명시. stale isStarted와실제유효run 불일치는고아데이터검증/복구로분리, 임의완료처리/데이터삭제로풀지않음. 삭제확인은선택이력및원문영구삭제/다른일정보존/점수aggregate보존을정확히알리고cancel무변경. 이슈에일회성started차단이기존동작변경임명시. + +### Q2 — 버튼 노출을 넘어선 삭제 원문과 공유 자원 경계 + +**전담 서브에이전트 질문** + +> Q2: CONTEXT/ADR0034는 개별삭제 시 Place·Preparation 원문 제거를 약속하지만 실제 일회성 DAO는 schedules 행만 삭제하고, preparationSchedules FK는 cascade가 없어 준비 단계가 있으면 실패할 수 있습니다. 반복 _removeRow는 단계만 지우고 custom preparationDefinition/고아 Place가 남을 수 있으며 Backup은 모든 recurring definitions를 캡처합니다. 권고: U01은 버튼만 열지 않고 이 삭제 무결성도 포함합니다. 하나의 DB transaction에서 선택 schedule/outcome/개별준비·관련 원문을 지우고, Place·Preparation definition은 다른 일정/반복 segment/사용자 default/template 등 실제 살아 있는 참조가 전혀 없는 해당 소유 데이터만 제거하며 공유 원문은 보존합니다. score aggregate 및 reset boundary는 불변, revision은 성공시 한번만 갱신, 실패 전체rollback, 동일ID 재시도는 멱등. 반복 exclusion에는 재생성 방지에 필요한 segment/slot/ordinal만 남기고 삭제 schedule ID/원문/점수 상세는 남기지 않습니다. 기존 외부 backup 파일은 건드리지 않으며 새backup에서 삭제한 개별원문 부재와 older backup restore 재등장을 검증합니다. 이 통합 범위/공유자원 보존 예외를 채택할까요? ADR0034의 ‘Place/Preparation 제거’는 ‘삭제 일정에 독점 소유되거나 더 이상 참조되지 않는 내용’으로 읽도록 root 문구 정합화가 필요합니다. + +**root 대리 답변** + +> Q2 통합범위채택. 실제FK/참조그래프확인해transaction에서선택일정+독점소유상세를제거,공유Place/definition/template/default/segment가참조하면보존. 단순이름일치로공유추정/삭제말고실제ID참조기준. 현삭제가영향주는orphan만정리, unrelated전체DB청소는범위아님. aggregate/resetboundary불변,revision성공시1회·no-op재시도0회, 모든실패rollback, 동일ID재시도멱등. exclusion은실제반복도메인에서재생성방지에필요한최소키만보존, 삭제상세원문없음. 외부backup불변,새backup삭제원문없음,오래된명시restore로재등장가능안내. ADR0034와CONTEXT해당문구정합화제안원문 보내주세요(root반영). 실제OS취소실패시DB삭제·부분결과는A15/D03정책재사용,별도원자성거짓보장금지. 이부분추가질문으로구체화해주세요. + +### Q3 — DB commit·OS 취소·stale 확인과 접근성 + +**전담 서브에이전트 질문 및 문구 제안** + +> Q3: 삭제 확인 후 DB commit과 OS 취소를 하나의 성공으로 표시하지 않겠습니다. 권고: 삭제 intent는 확인 시 schedule identity+최신 내용/상태 기준+현재 data generation을 고정하고 transaction에서 재검사합니다. 확인 뒤 수정/시작·완료/restore/reset/같은 ID 재생성이면 typed stale/conflict 후 재조회·다시 확인하며 대상 바뀐 삭제는 실행하지 않습니다(무관 일정 revision은 차단 사유 아님). commit 전 실패는 전부 유지, commit 후 데이터는 재생성/롤백하지 않고 목록에서 제거한 상태로 ‘이력 삭제됨, 알림 정리 확인 필요’와 명시 재시도를 제공합니다. OS 정리는 A15/D03 공유 owner+actual provider ID+내용 없는 pending ownership에 맡기고 성공 확인 뒤 최소 삭제ID도 제거합니다. journal 자체 실패/unknown ownership/응답없는 provider는 완료 표시 금지, timeout으로 owner해제·cancelAll·새 예약 중첩 금지입니다. 이력별 재시도는 DB를 다시 삭제하거나 revision 올리지 않고 같은 현재세대 cleanup만 재실행하며, oldgeneration callback은새데이터/UI/registry를건드리지않습니다. 화면은 swipe 외에 스크린리더/키보드로 접근 가능한 삭제 액션, KO/EN·200% 글자·연타 single-flight·확인취소 무변경을 포함하고, ‘되돌릴 수 없음’은 앱 내 undo 없음과 외부 옛백업 복원 가능성을 구분합니다. 이 계약을 채택할까요? +> +> 문구 제안: CONTEXT relationship은 “Deleting a Schedule removes its details and its exclusively owned content; content still owned or referenced by another Schedule, Recurring Schedule, default Preparation, or Preparation template is retained for that independent purpose. Only minimal content-free delivery ownership may remain while cancellation is unresolved, and it is removed after cancellation is confirmed.” ADR0034는 ‘atomically ... local delivery registrations’를 ‘The selected Schedule and its exclusively owned details are removed in one local-data transaction; shared content remains while independently referenced. Operating-system delivery cancellation is a separate recoverable operation, and unresolved cancellation retains only the minimum content-free ownership needed to finish cleanup, with no claim of completed delivery removal.’로 정합화 권고입니다. + +**root 대리 답변과 중요 보충** + +> Q3 채택. 핵심추가: DB삭제가실제취소ID를잃기전에A15/D03owner아래최소ownership이durably확인돼야함. journal쓰기실패로식별자보존불가면DB삭제commit하지않고safeerror; 이미durable보존된OS취소실패/unknown은DB삭제후partial가능. 응답없는OS Future는owner유지,UItimeout성공아님. 확인identity+내용/상태+generation재검증, 무관revision차이는차단않음. 동일ID새generation/재생성혼동금지, 단순멱등재시도와staleidentity구별. commit후DB재생성없고cleanup만재시도, 원문없는최소scheduleID는취소확인후제거. UI접근성/KOEN200%/singleflight/cancel정책모두채택. CONTEXT/ADR문구수정승인,root즉시반영하되기존문장위치와현재원문을보내주세요. 실제3문답+보충전부기록, 고정근거/검증/의존성확정후U01.md동결해주세요. + +**전담 서브에이전트 후속 근거 전달** + +> 현재 원문 위치: CONTEXT.md:433 `- Deleting a Schedule removes its name, Place, note, Preparation, Schedule Outcome detail, delivery registrations, and appearance in current backup data.` 다음434는 non-identifying aggregate,435는 older backup restore입니다. docs/adr/0034-retain-schedule-history-until-user-deletion.md:7 단일 Decision문단 중 `Deleting a Schedule will atomically remove its name, Place, note, Preparation relationship and content, detailed Schedule Outcome, and local delivery registrations, and the deleted Schedule will be absent from later OnTime Backups.` 문장입니다. 이 문장만 앞서 제안한 transaction/shared-content/recoverable OS문장으로 교체하되 ‘deleted Schedule is absent from later OnTime Backups’ 의미 유지하면 됩니다. Q3 보충의 DB commit 전 durable ownership 보존 실패차단도 U01에 명시하겠습니다. 신규 glossary term/새ADR는 필요 없습니다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 84525afa..23a0d75a 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -375,10 +375,11 @@ "Remote CI and merge pending" ], "sequence": 11, - "readback_verified": true, - "body_sha256": "290e625b9510c275e775bd8301b804ce2a57cae09716a6c7d469ec5b6dc0622f", + "readback_verified": false, + "body_sha256": "0abd28107d757b22229532d9a9d6659b549252f8ec26b70da2ee4586101ae92f", "progress_file": "plans/audit-2026-09-23/d03-journal-progress.json", - "validation_file": "plans/audit-2026-09-23/d03-final-validation.json" + "validation_file": "plans/audit-2026-09-23/d03-final-validation.json", + "integration_validation_file": "plans/audit-2026-09-23/d03-main-integration-validation.json" }, { "audit_id": "A06", @@ -675,16 +676,18 @@ "완료 이력도 개별 삭제 가능하게 한다.", "`schedule_detail.dart:108-123`은 notEnded만 삭제 허용. `CONTEXT.md:411-413`과 상충. 완료/비정상 이력의 상세를 삭제해도 점수 집계 정책을 보존하고, 기존 삭제 숨김 테스트를 수정." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_u01", + "issue_number": 611, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/611", "decisions_file": "plans/audit-2026-09-23/issues/U01.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 24 + "sequence": 24, + "readback_verified": true, + "issue_body_sha256": "5788a580428d73ebb9c21ee8d70c29299b824ba180e952f0aaf5b49b404eee7b" }, { "audit_id": "U03", @@ -1536,8 +1539,8 @@ "sequence": 64 } ], - "current_upstream_main": "2b02fb770324de291488f3c74fe503dc063e2459", - "upstream_integration_status": "flutter_and_android_ci_passed", + "current_upstream_main": "c7184b2a", + "upstream_integration_status": "latest_figma_merge_ci_pending", "upstream_integration_validation": { "full_tests": 678, "analyze": "no issues", From d06474d4c645794644075173f76afb9ff4445047 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 10:56:13 +0900 Subject: [PATCH 38/59] fix: deliver fresh preparation step transitions from the real timer --- lib/core/services/notification_service.dart | 57 +- .../app/bloc/schedule/schedule_bloc.dart | 241 +++++---- .../app/bloc/schedule/schedule_event.dart | 10 +- lib/presentation/app/screens/app.dart | 5 +- .../preparation_step_notification_test.dart | 280 ++++++++++ .../preparation_step_delivery_test.dart | 504 ++++++++++++++++++ .../app/bloc/schedule/schedule_bloc_test.dart | 11 +- 7 files changed, 998 insertions(+), 110 deletions(-) create mode 100644 test/core/services/preparation_step_notification_test.dart create mode 100644 test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart diff --git a/lib/core/services/notification_service.dart b/lib/core/services/notification_service.dart index be73bc42..230a00a2 100644 --- a/lib/core/services/notification_service.dart +++ b/lib/core/services/notification_service.dart @@ -1,4 +1,5 @@ import 'package:on_time_front/core/services/notification_routing.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:on_time_front/core/database/local_data_operation_gate.dart'; import 'dart:convert'; import 'package:on_time_front/domain/entities/notification_route_payload.dart'; @@ -29,11 +30,13 @@ class NotificationService { String Function()? localeProvider, bool? isIOSOverride, bool? isAndroidOverride, + AlarmOperationCoordinator? alarmOwner, }) : _localNotifications = localNotifications ?? FlutterLocalNotificationsPlugin(), _notificationTapRouter = notificationTapRouter ?? const NoopNotificationTapRouter(), _localeProvider = localeProvider, + _alarmOwner = alarmOwner ?? AlarmOperationCoordinator.shared, _isAndroidOverride = isAndroidOverride, _isIOSOverride = isIOSOverride; @@ -46,10 +49,12 @@ class NotificationService { bool isTimezoneInitialized = false, bool? isIOSOverride, bool? isAndroidOverride, + AlarmOperationCoordinator? alarmOwner, }) : _localNotifications = localNotifications, _notificationTapRouter = notificationTapRouter ?? const NoopNotificationTapRouter(), _localeProvider = localeProvider, + _alarmOwner = alarmOwner ?? AlarmOperationCoordinator.shared, _isAndroidOverride = isAndroidOverride, _isIOSOverride = isIOSOverride, _isFlutterLocalNotificationsInitialized = @@ -61,6 +66,7 @@ class NotificationService { 'on_time_front/native_alarm', ); + final AlarmOperationCoordinator _alarmOwner; final FlutterLocalNotificationsPlugin _localNotifications; NotificationTapRouter _notificationTapRouter; final String Function()? _localeProvider; @@ -343,12 +349,14 @@ class NotificationService { _isFlutterLocalNotificationsInitialized = true; } - Future showLocalNotification({ + Future showLocalNotification({ required String title, required String body, Map? payload, + bool Function()? isCurrent, }) async { await setupFlutterNotifications(); + if (!(isCurrent?.call() ?? true)) return false; await _localNotifications.show( id: Object.hash(title, body, DateTime.now().microsecondsSinceEpoch), title: title, @@ -370,6 +378,7 @@ class NotificationService { ), payload: encodeLocalNotificationPayload(payload), ); + return true; } Future showPreparationStepNotification({ @@ -377,20 +386,40 @@ class NotificationService { required String preparationName, required String scheduleId, required String stepId, + required bool Function() isCurrent, }) async { - if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { - return; - } - await showLocalNotification( - title: _locale == 'ko' ? '준비 단계가 바뀌었어요' : 'Preparation updated', - body: _locale == 'ko' - ? 'OnTime을 열어 다음 단계를 확인하세요.' - : 'Open OnTime to see the next step.', - payload: preparationStepNotificationPayload( - scheduleId: scheduleId, - stepId: stepId, - ), - ); + bool current() => + isCurrent() && + WidgetsBinding.instance.lifecycleState == AppLifecycleState.paused; + if (!current() || !_alarmOwner.canSchedule) return; + final lease = _alarmOwner.capture(); + await _alarmOwner.run(lease, () async { + if (!current()) return; + // Upcoming preparation-start preferences do not control an active run. + // This read never requests permission or opens a system prompt. + if (!await hasNotificationPermission()) { + AppLogger.debug( + '[NotificationService] step delivery skipped: permission', + ); + return; + } + if (!lease.isCurrent || !current()) return; + final submitted = await showLocalNotification( + title: _locale == 'ko' ? '준비 단계가 바뀌었어요' : 'Preparation updated', + body: _locale == 'ko' + ? 'OnTime을 열어 다음 단계를 확인하세요.' + : 'Open OnTime to see the next step.', + payload: preparationStepNotificationPayload( + scheduleId: scheduleId, + stepId: stepId, + ), + isCurrent: () => lease.isCurrent && current(), + ); + // Plugin completion is only submission, never proof the user saw it. + AppLogger.debug( + '[NotificationService] step delivery: ${submitted ? 'submitted' : 'skipped stale'}', + ); + }); } Future hasNotificationPermission() async { diff --git a/lib/presentation/app/bloc/schedule/schedule_bloc.dart b/lib/presentation/app/bloc/schedule/schedule_bloc.dart index aeeb7cbe..8d38d01a 100644 --- a/lib/presentation/app/bloc/schedule/schedule_bloc.dart +++ b/lib/presentation/app/bloc/schedule/schedule_bloc.dart @@ -2,7 +2,7 @@ import 'package:on_time_front/core/database/local_data_operation_gate.dart'; import 'dart:async'; import 'package:equatable/equatable.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/core/logging/app_logger.dart'; @@ -22,24 +22,27 @@ part 'schedule_state.dart'; typedef NowProvider = DateTime Function(); typedef NotifyPreparationStep = - void Function({ + FutureOr Function({ required String scheduleName, required String preparationName, required String scheduleId, required String stepId, + required bool Function() isCurrent, }); -void _defaultNotifyPreparationStep({ +Future _defaultNotifyPreparationStep({ required String scheduleName, required String preparationName, required String scheduleId, required String stepId, + required bool Function() isCurrent, }) { - NotificationService.instance.showPreparationStepNotification( + return NotificationService.instance.showPreparationStepNotification( scheduleName: scheduleName, preparationName: preparationName, scheduleId: scheduleId, stepId: stepId, + isCurrent: isCurrent, ); } @@ -62,10 +65,14 @@ class ScheduleBloc extends Bloc { this._schedulePreparationSessionUseCase, { NowProvider? nowProvider, NotifyPreparationStep? notifyPreparationStep, + Duration Function()? monotonicNow, + AppLifecycleState? Function()? lifecycleState, }) : _nowProvider = nowProvider ?? DateTime.now, _notifyPreparationStep = notifyPreparationStep ?? _defaultNotifyPreparationStep, super(const ScheduleState.initial()) { + _monotonicOverride = monotonicNow; + _lifecycleOverride = lifecycleState; _registerHandlers(); } @@ -116,8 +123,30 @@ class ScheduleBloc extends Bloc { DateTime? _lastSnapshotSavedAt; DateTime? _activePreparationRunStartedAt; List _activePreparationActionEvents = const []; - bool _suppressNextCatchUpStepNotification = false; - final Map> _notifiedStepIdsByScheduleId = {}; + // One delayed 1-second tick is tolerated; longer gaps are silent catch-up. + static const stepNotificationFreshness = Duration(seconds: 2); + // Normal wall/monotonic reads differ by tiny scheduling jitter. A 250 ms + // discontinuity is conservatively treated as a clock adjustment, not delivery. + static const stepClockTolerance = Duration(milliseconds: 250); + final Stopwatch _monotonicClock = Stopwatch()..start(); + Duration Function()? _monotonicOverride; + AppLifecycleState? Function()? _lifecycleOverride; + Duration get _monotonicNow => + _monotonicOverride?.call() ?? _monotonicClock.elapsed; + AppLifecycleState? get _lifecycle => _lifecycleOverride == null + ? WidgetsBinding.instance.lifecycleState + : _lifecycleOverride!(); + (Duration, DateTime, AppLifecycleState?)? _stepObservation; + int _stepRevision = 0; + (int, String, DateTime)? _stepRun; + final Set _attemptedSteps = {}; + + /// Called synchronously for every phase so paused -> inactive -> paused + /// between two timer callbacks cannot masquerade as continuous background. + void observeLifecycleState(AppLifecycleState phase) { + _stepObservation = null; + _stepRevision++; + } Object? _notificationPreparationOwner; Object? _notificationPreparationViewOwner; @@ -236,6 +265,8 @@ class ScheduleBloc extends Bloc { if (notificationPreparationId != null) return; final current = _captureBackgroundValidity(); if (!current()) return; + _stepRevision++; + _stepObservation = null; _scheduleStartTimer?.cancel(); _scheduleStartTimer = null; final now = _nowProvider(); @@ -253,7 +284,7 @@ class ScheduleBloc extends Bloc { _activeEarlyStartScheduleId = null; _lastSnapshotSavedAt = null; _clearActivePreparationRun(); - _notifiedStepIdsByScheduleId.clear(); + _attemptedSteps.clear(); return; } @@ -261,7 +292,7 @@ class ScheduleBloc extends Bloc { if (_currentScheduleId != null && _currentScheduleId != incoming.id) { await _clearPersistedState(_currentScheduleId!); if (!current()) return; - _notifiedStepIdsByScheduleId.remove(_currentScheduleId); + _clearActivePreparationRun(); } _currentScheduleId = incoming.id; @@ -285,7 +316,6 @@ class ScheduleBloc extends Bloc { _clearActivePreparationRun(); } if (!current()) return; - _initializeNotificationTracking(resolvedSchedule); if (_snapshotInvalidated) { _activeEarlyStartScheduleId = null; _clearActivePreparationRun(); @@ -360,7 +390,6 @@ class ScheduleBloc extends Bloc { await _startScheduleLocally(state.schedule!.id); if (!current()) return; emit(ScheduleState.started(state.schedule!)); - _initializeNotificationTracking(state.schedule!); _navigationService.push('/scheduleStart'); _activePreparationRunStartedAt ??= state.schedule!.preparationStartTime; _startPreparationTimer(); @@ -456,7 +485,6 @@ class ScheduleBloc extends Bloc { _scheduleStartTimer?.cancel(); _scheduleStartTimer = null; _stopPreparationTimer(); - _initializeNotificationTracking(schedule); await _restoreFromSnapshotIfValid(schedule, isCurrent: isCurrent); if (!isCurrent()) return; if (_snapshotInvalidated) { @@ -512,46 +540,29 @@ class ScheduleBloc extends Bloc { _startPreparationTimer(); } - Future _onTick(ScheduleTick event, Emitter emit) async { - if (_notificationPromptOwner != null) return; - if (state.schedule == null) return; - final oldStepId = state.schedule!.preparation.currentStep?.id; - final updatedPreparation = state.schedule!.preparation.timeElapsed( - event.elapsed, - ); - AppLogger.debug('elapsedTime: ${updatedPreparation.elapsedTime}'); - - final newSchedule = - ScheduleWithPreparationEntity.fromScheduleAndPreparationEntity( - state.schedule!, - updatedPreparation, - ); - - final shouldSuppressStepNotification = _suppressNextCatchUpStepNotification; - _suppressNextCatchUpStepNotification = false; - if (!shouldSuppressStepNotification) { - _checkAndNotifyStepChange(state.schedule!, newSchedule); - } - - emit(state.copyWith(schedule: newSchedule)); - final stepChanged = oldStepId != newSchedule.preparation.currentStep?.id; - await _saveTimedPreparationSnapshot(newSchedule, force: stepChanged); - } + Future _onTick(ScheduleTick event, Emitter emit) => + _onPreparationTimeRefreshRequested( + const SchedulePreparationTimeRefreshRequested(), + emit, + ); Future _onPreparationTimeRefreshRequested( SchedulePreparationTimeRefreshRequested event, Emitter emit, ) async { if (_notificationPromptOwner != null || state.schedule == null) return; + if (event.origin != PreparationRefreshOrigin.periodic) _stepRevision++; if (state.schedule!.preparation.isAllStepsDone) return; final startedAt = _activePreparationRunStartedAt; if (startedAt == null) return; + final previous = state.schedule!; + final observedAt = _nowProvider(); final refreshedPreparation = _derivePreparationRun( state.schedule!.preparation, startedAt: startedAt, actionEvents: _activePreparationActionEvents, - now: _nowProvider(), + now: observedAt, ); final refreshedSchedule = ScheduleWithPreparationEntity.fromScheduleAndPreparationEntity( @@ -559,6 +570,12 @@ class ScheduleBloc extends Bloc { refreshedPreparation, ); emit(state.copyWith(schedule: refreshedSchedule)); + _observeStepTransition( + previous, + refreshedSchedule, + event.origin, + observedAt, + ); await _saveTimedPreparationSnapshot(refreshedSchedule); } @@ -568,6 +585,8 @@ class ScheduleBloc extends Bloc { ) async { if (_notificationPromptOwner != null || state.schedule == null) return; if (state.schedule!.preparation.isAllStepsDone) return; + _stepObservation = null; + _stepRevision++; final now = _nowProvider(); _activePreparationRunStartedAt ??= state.isEarlyStarted ? now.subtract(state.schedule!.preparation.elapsedTime) @@ -595,6 +614,7 @@ class ScheduleBloc extends Bloc { updated, ); emit(state.copyWith(schedule: newSchedule)); + _stepObservation = (_monotonicNow, now, _lifecycle); await _saveTimedPreparationSnapshot(newSchedule, force: true); } @@ -605,6 +625,8 @@ class ScheduleBloc extends Bloc { final current = _captureBackgroundValidity(); if (!current() || state.schedule == null) return; final scheduleId = state.schedule!.id; + _stepObservation = null; + _stepRevision++; try { await _schedulePreparationSessionUseCase.finishSchedulePreparation( scheduleId, @@ -623,6 +645,8 @@ class ScheduleBloc extends Bloc { _activeEarlyStartScheduleId = null; _lastSnapshotSavedAt = null; _clearActivePreparationRun(); + _attemptedSteps.clear(); + _stepRun = null; emit(const ScheduleState.notExists()); if (restoreNearest) add(const ScheduleSubscriptionRequested()); } catch (error) { @@ -660,19 +684,21 @@ class ScheduleBloc extends Bloc { _activePreparationRunStartedAt ??= state.isEarlyStarted ? _nowProvider().subtract(state.schedule!.preparation.elapsedTime) : state.schedule!.preparationStartTime; - final elapsedTimeAfterLastTick = state.isEarlyStarted - ? Duration.zero - : _nowProvider().difference(state.schedule!.preparationStartTime) - - state.schedule!.preparation.elapsedTime; - if (elapsedTimeAfterLastTick > Duration.zero) { - AppLogger.debug('elapsedTimeAfterLastTick: $elapsedTimeAfterLastTick'); + _stepObservation = null; + _stepRevision++; + add( + const SchedulePreparationTimeRefreshRequested( + origin: PreparationRefreshOrigin.restore, + ), + ); + _preparationTimer = Timer.periodic(const Duration(seconds: 1), (_) { if (!isClosed) { - _suppressNextCatchUpStepNotification = true; - add(ScheduleTick(elapsedTimeAfterLastTick)); + add( + const SchedulePreparationTimeRefreshRequested( + origin: PreparationRefreshOrigin.periodic, + ), + ); } - } - _preparationTimer = Timer.periodic(Duration(seconds: 1), (_) { - if (!isClosed) add(const SchedulePreparationTimeRefreshRequested()); }); } @@ -687,6 +713,9 @@ class ScheduleBloc extends Bloc { _upcomingScheduleSubscription?.cancel(); _scheduleStartTimer?.cancel(); _stopPreparationTimer(); + _stepRevision++; + _attemptedSteps.clear(); + _stepRun = null; return super.close(); } @@ -816,6 +845,8 @@ class ScheduleBloc extends Bloc { } void _clearActivePreparationRun() { + _stepObservation = null; + _stepRevision++; _activePreparationRunStartedAt = null; _activePreparationActionEvents = const []; } @@ -841,54 +872,86 @@ class ScheduleBloc extends Bloc { doneStatus == ScheduleDoneStatus.abnormalEnd; } - void _initializeNotificationTracking(ScheduleWithPreparationEntity schedule) { - final scheduleId = schedule.id; - if (!_notifiedStepIdsByScheduleId.containsKey(scheduleId)) { - _notifiedStepIdsByScheduleId[scheduleId] = {}; - } - } - - void _checkAndNotifyStepChange( - ScheduleWithPreparationEntity oldSchedule, - ScheduleWithPreparationEntity newSchedule, + void _observeStepTransition( + ScheduleWithPreparationEntity before, + ScheduleWithPreparationEntity after, + PreparationRefreshOrigin origin, + DateTime wall, ) { - if (newSchedule.preparation.isAllStepsDone) { + final mono = _monotonicNow; + final phase = _lifecycle; + final previous = _stepObservation; + final run = ( + LocalDataOperationGate.shared.generation, + after.id, + _activePreparationRunStartedAt!, + ); + if (_stepRun != run) { + _stepRun = run; + _attemptedSteps.clear(); + } + _stepObservation = (mono, wall, phase); + if (origin != PreparationRefreshOrigin.periodic || + previous == null || + phase != AppLifecycleState.paused || + previous.$3 != phase) { return; } - - final scheduleId = newSchedule.id; - final oldCurrentStep = oldSchedule.preparation.currentStep; - final newCurrentStep = newSchedule.preparation.currentStep; - - if (oldCurrentStep?.id == newCurrentStep?.id || newCurrentStep == null) { + final delta = mono - previous.$1; + if (delta < Duration.zero || + delta > stepNotificationFreshness || + (wall.difference(previous.$2) - delta).abs() > stepClockTolerance) { return; } - - final firstStep = newSchedule.preparation.preparationStepList.isNotEmpty - ? newSchedule.preparation.preparationStepList.first - : null; - if (firstStep != null && newCurrentStep.id == firstStep.id) { + final steps = after.preparation.preparationStepList; + final oldIndex = steps.indexWhere( + (s) => s.id == before.preparation.currentStep?.id, + ); + final step = after.preparation.currentStep; + if (step == null || + oldIndex < 0 || + steps.indexWhere((s) => s.id == step.id) != oldIndex + 1) { return; } - - final notifiedStepIds = _notifiedStepIdsByScheduleId[scheduleId] ?? {}; - if (notifiedStepIds.contains(newCurrentStep.id)) { - return; + // Consume before the first await; failure is not an invitation to replay. + if (!_attemptedSteps.add(step.id)) return; + final revision = _stepRevision; + final backgroundCurrent = _captureBackgroundValidity(); + bool current() => + backgroundCurrent() && + !LocalDataOperationGate.shared.isInvalidated && + !LocalDataOperationGate.shared.isReplacingData && + revision == _stepRevision && + _stepRun == run && + state.schedule?.id == after.id && + _activePreparationRunStartedAt == run.$3 && + state.schedule?.preparation.currentStep?.id == step.id && + _lifecycle == AppLifecycleState.paused && + _monotonicNow - mono <= stepNotificationFreshness && + _monotonicNow >= mono && + (_nowProvider().difference(wall) - (_monotonicNow - mono)).abs() <= + stepClockTolerance; + unawaited(_submitStep(after, step.id, current)); + } + + Future _submitStep( + ScheduleWithPreparationEntity schedule, + String stepId, + bool Function() current, + ) async { + if (!current()) return; + try { + await _notifyPreparationStep( + scheduleName: schedule.scheduleName, + preparationName: '', + scheduleId: schedule.id, + stepId: stepId, + isCurrent: current, + ); + } catch (error) { + AppLogger.debug( + '[ScheduleBloc] step delivery failed: ${error.runtimeType}', + ); } - - _notifyPreparationStep( - scheduleName: newSchedule.scheduleName, - preparationName: newCurrentStep.preparationName, - scheduleId: scheduleId, - stepId: newCurrentStep.id, - ); - - notifiedStepIds.add(newCurrentStep.id); - _notifiedStepIdsByScheduleId[scheduleId] = notifiedStepIds; - - AppLogger.debug( - '[ScheduleBloc] preparation step notification shown ' - 'scheduleId=$scheduleId stepId=${newCurrentStep.id}', - ); } } diff --git a/lib/presentation/app/bloc/schedule/schedule_event.dart b/lib/presentation/app/bloc/schedule/schedule_event.dart index 7d74b724..446125b3 100644 --- a/lib/presentation/app/bloc/schedule/schedule_event.dart +++ b/lib/presentation/app/bloc/schedule/schedule_event.dart @@ -67,11 +67,17 @@ final class ScheduleTick extends ScheduleEvent { List get props => [elapsed]; } +enum PreparationRefreshOrigin { periodic, resume, restore, manual } + final class SchedulePreparationTimeRefreshRequested extends ScheduleEvent { - const SchedulePreparationTimeRefreshRequested(); + const SchedulePreparationTimeRefreshRequested({ + this.origin = PreparationRefreshOrigin.manual, + }); + + final PreparationRefreshOrigin origin; @override - List get props => []; + List get props => [origin]; } final class ScheduleStepSkipped extends ScheduleEvent { diff --git a/lib/presentation/app/screens/app.dart b/lib/presentation/app/screens/app.dart index 71f42433..c21f6d9c 100644 --- a/lib/presentation/app/screens/app.dart +++ b/lib/presentation/app/screens/app.dart @@ -151,10 +151,13 @@ class _AppRouterViewState extends State<_AppRouterView> @override void didChangeAppLifecycleState(AppLifecycleState state) { AppLogger.debug('$_logTag lifecycle state=$state'); + context.read().observeLifecycleState(state); if (state != AppLifecycleState.resumed) return; unawaited(HardwareKeyboard.instance.syncKeyboardState().catchError((_) {})); context.read().add( - const SchedulePreparationTimeRefreshRequested(), + const SchedulePreparationTimeRefreshRequested( + origin: PreparationRefreshOrigin.resume, + ), ); unawaited( getIt.get().dispatchPendingLaunchPayload(), diff --git a/test/core/services/preparation_step_notification_test.dart b/test/core/services/preparation_step_notification_test.dart new file mode 100644 index 00000000..47e0f274 --- /dev/null +++ b/test/core/services/preparation_step_notification_test.dart @@ -0,0 +1,280 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/notification_service.dart'; +import 'package:drift/native.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/core/constants/local_profile.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; + +void main() { + final binding = TestWidgetsFlutterBinding.ensureInitialized(); + const notifications = MethodChannel( + 'dexterous.com/flutter/local_notifications', + ); + const permissions = MethodChannel('flutter.baseflow.com/permissions/methods'); + final calls = []; + final permissionCalls = []; + late AppDatabase database; + late LocalDataOperationGate gate; + late AlarmOperationCoordinator owner; + late NotificationService service; + var displayAllowed = true, permissionFailure = false, showFailure = false; + var current = true; + var locale = 'en'; + Completer? permissionBarrier, setupBarrier, showBarrier; + setUp(() async { + calls.clear(); + permissionCalls.clear(); + displayAllowed = true; + permissionFailure = false; + showFailure = false; + current = true; + locale = 'en'; + permissionBarrier = null; + setupBarrier = null; + showBarrier = null; + gate = LocalDataOperationGate(); + owner = AlarmOperationCoordinator(gate); + debugDefaultTargetPlatformOverride = TargetPlatform.android; + AndroidFlutterLocalNotificationsPlugin.registerWith(); + binding.handleAppLifecycleStateChanged(AppLifecycleState.paused); + database = AppDatabase.forTesting(NativeDatabase.memory()); + await database.userDao.createUser( + const UserEntity( + id: localProfileId, + spareTime: Duration.zero, + note: '', + eligibleOutcomeCount: 0, + onTimeOutcomeCount: 0, + ), + ); + await database.userDao.updateAlarmSettings( + userId: localProfileId, + enabled: false, + ); + final messenger = + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + messenger.setMockMethodCallHandler(permissions, (call) async { + permissionCalls.add(call.method); + await permissionBarrier?.future; + if (permissionFailure) throw PlatformException(code: 'query_unavailable'); + return displayAllowed ? 1 : 0; + }); + messenger.setMockMethodCallHandler(notifications, (call) async { + calls.add(call); + if (call.method == 'initialize') { + await setupBarrier?.future; + return true; + } + if (call.method == 'show') { + await showBarrier?.future; + if (showFailure) throw PlatformException(code: 'synthetic_failure'); + } + return null; + }); + service = NotificationService.test( + localNotifications: FlutterLocalNotificationsPlugin(), + isAndroidOverride: true, + isIOSOverride: false, + isFlutterLocalNotificationsInitialized: true, + alarmOwner: owner, + localeProvider: () => locale, + ); + }); + tearDown(() async { + await database.close(); + binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); + debugDefaultTargetPlatformOverride = null; + final messenger = + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + messenger.setMockMethodCallHandler(permissions, null); + messenger.setMockMethodCallHandler(notifications, null); + owner.dispose(); + }); + Future show() => service.showPreparationStepNotification( + scheduleName: 'SECRET schedule', + preparationName: 'SECRET step', + scheduleId: 'occurrence', + stepId: 'step', + isCurrent: () => current, + ); + List submissions() => + calls.where((c) => c.method == 'show').toList(); + + for (final language in ['ko', 'en']) { + for (final detailed in [false, true]) { + test( + '$language detailed=$detailed retains private text/minimum payload while upcoming OFF', + () async { + locale = language; + // These preferences may control future start alarms, never this active run. + await database.userDao.updateDetailedNotificationContent( + userId: localProfileId, + enabled: detailed, + ); + final settings = await database.userDao.getAlarmSettings( + localProfileId, + ); + expect(settings.enabled, isFalse); + expect(settings.detailedNotificationContent, detailed); + await show(); + final args = submissions().single.arguments as Map; + expect( + args['title'], + language == 'ko' ? '준비 단계가 바뀌었어요' : 'Preparation updated', + ); + expect( + args['body'], + language == 'ko' + ? 'OnTime을 열어 다음 단계를 확인하세요.' + : 'Open OnTime to see the next step.', + ); + expect(jsonDecode(args['payload'] as String), { + 'type': 'preparation_step', + 'scheduleId': 'occurrence', + 'stepId': 'step', + }); + expect(args.toString(), isNot(contains('SECRET'))); + expect(permissionCalls, ['checkPermissionStatus']); + }, + ); + } + } + test('display denied skips show without permission prompt', () async { + displayAllowed = false; + await show(); + expect(submissions(), isEmpty); + expect(permissionCalls, ['checkPermissionStatus']); + }); + test( + 'permission query error propagates to the observed caller, no show', + () async { + permissionFailure = true; + await expectLater(show(), throwsA(isA())); + expect(submissions(), isEmpty); + expect(permissionCalls, ['checkPermissionStatus']); + }, + ); + for (final phase in [ + AppLifecycleState.resumed, + AppLifecycleState.inactive, + AppLifecycleState.hidden, + AppLifecycleState.detached, + ]) { + test('$phase never queries or submits', () async { + binding.handleAppLifecycleStateChanged(phase); + await show(); + expect(submissions(), isEmpty); + expect(permissionCalls, isEmpty); + }); + } + for (final interruption in ['session', 'resume', 'replacement']) { + test('permission await rechecks $interruption before show', () async { + permissionBarrier = Completer(); + final pending = show(); + await pumpEventQueue(); + expect(permissionCalls, ['checkPermissionStatus']); + switch (interruption) { + case 'session': + current = false; + case 'resume': + binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); + case 'replacement': + await gate.run(() async {}, replacesData: true); + } + permissionBarrier!.complete(); + await pending; + expect(submissions(), isEmpty); + }); + } + test( + 'setup await rechecks validity immediately before plugin submission', + () async { + setupBarrier = Completer(); + service = NotificationService.test( + localNotifications: FlutterLocalNotificationsPlugin(), + isAndroidOverride: true, + isIOSOverride: false, + alarmOwner: owner, + ); + final pending = show(); + await pumpEventQueue(); + expect(calls.any((c) => c.method == 'initialize'), isTrue); + current = false; + setupBarrier!.complete(); + await pending; + expect(submissions(), isEmpty); + final args = + calls.singleWhere((c) => c.method == 'initialize').arguments as Map; + // iOS initialization never implicitly asks for authorization either. + expect(args.toString(), isNot(contains('requestAlertPermission: true'))); + }, + ); + test( + 'show failure propagates without releasing owner early or unhandled error', + () async { + showFailure = true; + await expectLater(show(), throwsA(isA())); + expect(submissions(), hasLength(1)); + var cleaned = false; + await owner.cleanup(() async { + cleaned = true; + }); + expect(cleaned, isTrue); + }, + ); + test( + 'reset cleanup waits for actual in-flight plugin effect, then cancels', + () async { + showBarrier = Completer(); + final pending = show(); + await pumpEventQueue(); + expect(submissions(), hasLength(1)); + gate.invalidate(); + var cleaned = false; + final cleanup = owner.cleanup(() async { + await service.cancelAll(); + cleaned = true; + }); + await pumpEventQueue(); + expect(cleaned, isFalse); + expect(calls.where((c) => c.method == 'cancelAll'), isEmpty); + showBarrier!.complete(); + await pending; + await cleanup; + expect(cleaned, isTrue); + expect( + calls + .map((c) => c.method) + .where((m) => m == 'show' || m == 'cancelAll'), + ['show', 'cancelAll'], + ); + }, + ); + test( + 'queued old-generation step is rejected before any platform call', + () async { + final hold = Completer(); + final preceding = owner.cleanup(() => hold.future); + final pending = show(); + final expectation = expectLater( + pending, + throwsA(isA()), + ); + await pumpEventQueue(); + gate.invalidate(); + hold.complete(); + await preceding; + await expectation; + expect(submissions(), isEmpty); + expect(permissionCalls, isEmpty); + }, + ); +} diff --git a/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart b/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart new file mode 100644 index 00000000..8e547ba7 --- /dev/null +++ b/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart @@ -0,0 +1,504 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/core/services/notification_service.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/services/navigation_service.dart'; +import 'package:on_time_front/domain/entities/early_start_session_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_action_event_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_step_with_time_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_with_preparation_entity.dart'; +import 'package:on_time_front/domain/use-cases/get_nearest_upcoming_schedule_use_case.dart'; +import 'package:on_time_front/domain/use-cases/schedule_preparation_session_use_case.dart'; +import 'package:on_time_front/presentation/app/bloc/schedule/schedule_bloc.dart'; +import 'schedule_bloc_test.dart' show buildSchedule; + +class _Session implements SchedulePreparationSessionUseCase { + @override + Future getEarlyStartSession(String id) async => + null; + @override + Future restoreTimedPreparationIfValid( + ScheduleWithPreparationEntity schedule, { + required DateTime now, + RestoredSessionCallback? onRestoredSession, + void Function()? onInvalidated, + }) async => schedule; + @override + Future saveTimedPreparationSnapshot( + ScheduleWithPreparationEntity schedule, { + DateTime? savedAt, + DateTime? startedAt, + List actionEvents = const [], + }) async {} + @override + Future clearPersistedState(String id) async {} + @override + Future finishSchedulePreparation( + String id, { + required int latenessTime, + }) async {} + @override + Future startSchedulePreparation(String id) async {} + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +class _Nearest implements GetNearestUpcomingScheduleUseCase { + @override + Stream call() => const Stream.empty(); +} + +class _Navigation extends NavigationService { + @override + void push(String routeName, {Object? extra}) {} +} + +class _Harness { + _Harness( + this.tester, { + this.phase = AppLifecycleState.paused, + FutureOr Function(bool Function())? submit, + NotifyPreparationStep? delivery, + }) { + bloc = ScheduleBloc.test( + _Nearest(), + _Navigation(), + _Session(), + nowProvider: () => wall, + monotonicNow: () => mono, + lifecycleState: () => phase, + notifyPreparationStep: + ({ + required scheduleName, + required preparationName, + required scheduleId, + required stepId, + required isCurrent, + }) { + attempts.add((scheduleId, stepId)); + if (delivery != null) { + return delivery( + scheduleName: scheduleName, + preparationName: preparationName, + scheduleId: scheduleId, + stepId: stepId, + isCurrent: isCurrent, + ); + } + return submit?.call(isCurrent); + }, + ); + } + final WidgetTester tester; + DateTime wall = DateTime(2026, 9, 24, 10); + Duration mono = Duration.zero; + AppLifecycleState? phase; + late final ScheduleBloc bloc; + final attempts = <(String, String)>[]; + Future start({ + String id = 'occurrence', + int elapsed = 1, + int count = 4, + }) async { + final steps = [ + for (var i = 1; i <= count; i++) + PreparationStepWithTimeEntity( + id: 's$i', + preparationName: 'private $i', + preparationTime: const Duration(seconds: 3), + nextPreparationId: i == count ? null : 's${i + 1}', + ), + ]; + bloc.add( + ScheduleUpcomingReceived( + buildSchedule( + id: id, + scheduleTime: wall.add(Duration(seconds: count * 3 - elapsed)), + moveTime: Duration.zero, + scheduleSpareTime: Duration.zero, + steps: steps, + ), + ), + ); + await tester.pump(); + } + + Future tick({ + Duration wallDelta = const Duration(seconds: 1), + Duration monoDelta = const Duration(seconds: 1), + }) async { + wall = wall.add(wallDelta); + mono += monoDelta; + // This fires the real production Timer.periodic, never a synthetic Tick. + await tester.pump(const Duration(seconds: 1)); + } + + void changePhase(AppLifecycleState next) { + phase = next; + bloc.observeLifecycleState(next); + } + + Future close() async { + await tester.runAsync(() => bloc.close()); + } +} + +void main() { + testWidgets('actual periodic refresh submits a fresh paused step once', ( + tester, + ) async { + final h = _Harness(tester); + await h.start(); + await h.tick(); + await h.tick(); + await h.tick(); + expect(h.bloc.state.schedule!.preparation.currentStep!.id, 's2'); + expect(h.attempts, [('occurrence', 's2')]); + await h.close(); + }); + testWidgets( + 'actual periodic timer reaches plugin show once with private content', + (tester) async { + const channel = MethodChannel( + 'dexterous.com/flutter/local_notifications', + ); + const permissionChannel = MethodChannel( + 'flutter.baseflow.com/permissions/methods', + ); + final submissions = []; + final permissionMethods = []; + final messenger = + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + debugDefaultTargetPlatformOverride = TargetPlatform.android; + AndroidFlutterLocalNotificationsPlugin.registerWith(); + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused); + messenger.setMockMethodCallHandler(channel, (call) async { + if (call.method == 'show') submissions.add(call.arguments as Map); + return null; + }); + messenger.setMockMethodCallHandler(permissionChannel, (call) async { + permissionMethods.add(call.method); + return 1; + }); + final owner = AlarmOperationCoordinator(LocalDataOperationGate()); + final service = NotificationService.test( + localNotifications: FlutterLocalNotificationsPlugin(), + isAndroidOverride: true, + isIOSOverride: false, + isFlutterLocalNotificationsInitialized: true, + alarmOwner: owner, + localeProvider: () => 'ko', + ); + final h = _Harness( + tester, + delivery: service.showPreparationStepNotification, + ); + try { + await h.start(); + await h.tick(); + await h.tick(); + await h.tick(); + await tester.pump(); + expect(submissions, hasLength(1)); + expect(submissions.single['title'], '준비 단계가 바뀌었어요'); + expect(jsonDecode(submissions.single['payload'] as String), { + 'type': 'preparation_step', + 'scheduleId': 'occurrence', + 'stepId': 's2', + }); + expect(submissions.single.toString(), isNot(contains('private'))); + expect(permissionMethods, ['checkPermissionStatus']); + } finally { + await h.close(); + owner.dispose(); + messenger.setMockMethodCallHandler(channel, null); + messenger.setMockMethodCallHandler(permissionChannel, null); + tester.binding.handleAppLifecycleStateChanged( + AppLifecycleState.resumed, + ); + debugDefaultTargetPlatformOverride = null; + } + }, + ); + for (final phase in [ + AppLifecycleState.resumed, + AppLifecycleState.inactive, + AppLifecycleState.hidden, + AppLifecycleState.detached, + null, + ]) { + testWidgets('$phase updates UI silently without later replay', ( + tester, + ) async { + final h = _Harness(tester, phase: phase); + await h.start(); + await h.tick(); + await h.tick(); + expect(h.bloc.state.schedule!.preparation.currentStep!.id, 's2'); + expect(h.attempts, isEmpty); + h.changePhase(AppLifecycleState.paused); + await h.tick(); + expect(h.attempts, isEmpty); + await h.tick(); + await h.tick(); + expect(h.attempts, [('occurrence', 's3')]); + await h.close(); + }); + } + for (final delta in [ + const Duration(seconds: 2), + const Duration(microseconds: 2000001), + ]) { + testWidgets('freshness boundary $delta', (tester) async { + final h = _Harness(tester); + await h.start(); + await h.tick(wallDelta: delta, monoDelta: delta); + expect(h.attempts.length, delta == const Duration(seconds: 2) ? 1 : 0); + await h.tick(); + expect(h.attempts.length, delta == const Duration(seconds: 2) ? 1 : 0); + await h.close(); + }); + } + for (final jitter in [ + const Duration(milliseconds: 250), + const Duration(microseconds: 250001), + ]) { + testWidgets('wall/monotonic tolerance boundary $jitter', (tester) async { + final h = _Harness(tester); + await h.start(elapsed: 2); + await h.tick(wallDelta: const Duration(seconds: 1) + jitter); + expect( + h.attempts.length, + jitter == const Duration(milliseconds: 250) ? 1 : 0, + ); + await h.close(); + }); + } + testWidgets('phase roundtrip between observations resets continuity', ( + tester, + ) async { + final h = _Harness(tester); + await h.start(); + await h.tick(); + h.changePhase(AppLifecycleState.inactive); + h.changePhase(AppLifecycleState.paused); + await h.tick(); + expect(h.attempts, isEmpty); + await h.tick(); + expect(h.attempts, isEmpty); + await h.close(); + }); + for (final wallDelta in [ + const Duration(seconds: 3), + const Duration(seconds: -1), + const Duration(seconds: 7), + ]) { + testWidgets('clock jump $wallDelta does not submit stale step', ( + tester, + ) async { + final h = _Harness(tester); + await h.start(); + await h.tick(); + await h.tick(wallDelta: wallDelta); + expect(h.attempts, isEmpty); + await h.close(); + }); + } + testWidgets('resume catch-up and legacy Tick cannot replay or double count', ( + tester, + ) async { + final h = _Harness(tester); + await h.start(); + await h.tick(); + h.wall = h.wall.add(const Duration(seconds: 4)); + h.mono += const Duration(seconds: 4); + h.bloc.add( + const SchedulePreparationTimeRefreshRequested( + origin: PreparationRefreshOrigin.resume, + ), + ); + h.bloc.add(const ScheduleTick(Duration(hours: 1))); + await tester.pump(); + expect(h.bloc.state.schedule!.preparation.currentStep!.id, 's3'); + expect( + h.bloc.state.schedule!.preparation.elapsedTime, + const Duration(seconds: 6), + ); + expect(h.attempts, isEmpty); + await h.tick(); + expect(h.attempts, isEmpty); + await h.close(); + }); + testWidgets( + 'restore is silent then future adjacent boundary remains eligible', + (tester) async { + final h = _Harness(tester); + await h.start(elapsed: 4); + expect(h.bloc.state.schedule!.preparation.currentStep!.id, 's2'); + expect(h.attempts, isEmpty); + await h.tick(); + await h.tick(); + expect(h.attempts, [('occurrence', 's3')]); + await h.close(); + }, + ); + testWidgets('skip is silent, next automatic transition is independent', ( + tester, + ) async { + final h = _Harness(tester); + await h.start(); + await h.tick(); + h.bloc.add(const ScheduleStepSkipped()); + await tester.pump(); + expect(h.attempts, isEmpty); + await h.tick(); + await h.tick(); + await h.tick(); + expect(h.attempts, [('occurrence', 's3')]); + await h.close(); + }); + testWidgets('first and final transitions produce no notification', ( + tester, + ) async { + final h = _Harness(tester); + await h.start(count: 1); + await h.tick(); + await h.tick(); + await h.tick(); + expect(h.bloc.state.schedule!.preparation.isAllStepsDone, isTrue); + expect(h.attempts, isEmpty); + await h.close(); + }); + for (final asyncError in [false, true]) { + testWidgets( + 'delivery ${asyncError ? 'Future error' : 'sync throw'} consumes candidate but next step proceeds', + (tester) async { + var count = 0; + final h = _Harness( + tester, + submit: (_) { + if (++count == 1) { + if (asyncError) { + return Future.error(StateError('synthetic')); + } + throw StateError('synthetic'); + } + }, + ); + await h.start(); + for (var i = 0; i < 5; i++) { + await h.tick(); + } + expect(h.attempts, [('occurrence', 's2'), ('occurrence', 's3')]); + expect(tester.takeException(), isNull); + await h.close(); + }, + ); + } + for (final interruption in [ + 'resume', + 'prompt', + 'delete', + 'finish', + 'generation', + 'stale', + ]) { + testWidgets('await validity rejects $interruption', (tester) async { + final barrier = Completer(); + final submitted = []; + final h = _Harness( + tester, + submit: (current) async { + await barrier.future; + submitted.add(current()); + }, + ); + await h.start(); + await h.tick(); + await h.tick(); + expect(h.attempts, hasLength(1)); + switch (interruption) { + case 'resume': + h.changePhase(AppLifecycleState.resumed); + case 'prompt': + h.bloc.presentNotificationPrompt( + h.bloc.state.schedule!, + Object(), + () => true, + ); + case 'delete': + h.bloc.add(const ScheduleUpcomingReceived(null)); + case 'finish': + h.bloc.add(const ScheduleFinished(0)); + case 'generation': + await LocalDataOperationGate.shared.run( + () async {}, + replacesData: true, + ); + case 'stale': + h.mono += const Duration(seconds: 3); + h.wall = h.wall.add(const Duration(seconds: 3)); + } + await tester.pump(); + barrier.complete(); + await tester.pump(); + expect(submitted, [false]); + await h.close(); + }); + } + testWidgets( + 'same occurrence new run resets dedupe, restore does not replay', + (tester) async { + final h = _Harness(tester); + await h.start(); + await h.tick(); + await h.tick(); + await h.start(elapsed: 3); + await h.tick(); + expect(h.attempts, [('occurrence', 's2')]); + h.bloc.add(const ScheduleFinished(0)); + await tester.pump(); + await h.start(); + await h.tick(); + await h.tick(); + expect(h.attempts, [('occurrence', 's2'), ('occurrence', 's2')]); + await h.close(); + }, + ); + testWidgets( + 'replacement generation with reused run and step IDs has independent attempts', + (tester) async { + final h = _Harness(tester); + await h.start(); + await h.tick(); + await h.tick(); + await LocalDataOperationGate.shared.run(() async {}, replacesData: true); + h.wall = h.wall.subtract(const Duration(seconds: 2)); + await h.start(); + expect(h.attempts, hasLength(1)); + await h.tick(); + await h.tick(); + expect(h.attempts, [('occurrence', 's2'), ('occurrence', 's2')]); + await h.close(); + }, + ); + testWidgets('occurrences sharing step IDs have independent attempts', ( + tester, + ) async { + final h = _Harness(tester); + await h.start(); + await h.tick(); + await h.tick(); + await h.start(id: 'next-occurrence'); + await h.tick(); + await h.tick(); + expect(h.attempts, [('occurrence', 's2'), ('next-occurrence', 's2')]); + await h.close(); + }); +} diff --git a/test/presentation/app/bloc/schedule/schedule_bloc_test.dart b/test/presentation/app/bloc/schedule/schedule_bloc_test.dart index d772da37..13419f92 100644 --- a/test/presentation/app/bloc/schedule/schedule_bloc_test.dart +++ b/test/presentation/app/bloc/schedule/schedule_bloc_test.dart @@ -511,6 +511,7 @@ TimedPreparationSnapshotEntity buildSnapshot({ } void main() { + TestWidgetsFlutterBinding.ensureInitialized(); group('ScheduleBloc preparation runtime flow', () { late StreamController controller; late SpyNavigationService navigationService; @@ -582,6 +583,7 @@ void main() { required preparationName, required scheduleId, required stepId, + required isCurrent, }) { notifiedStepIds.add(stepId); }, @@ -840,7 +842,7 @@ void main() { ); test( - 'entering ongoing applies catch-up tick and accepts later ticks', + 'entering ongoing and legacy refresh derive elapsed from wall clock', () async { final schedule = buildSchedule( id: 'tick', @@ -863,6 +865,7 @@ void main() { final caughtUpElapsed = bloc.state.schedule!.preparation.elapsedTime; expect(caughtUpElapsed, const Duration(seconds: 2)); + now = now.add(const Duration(seconds: 1)); bloc.add(const ScheduleTick(Duration(seconds: 1))); await Future.delayed(Duration.zero); expect( @@ -1173,7 +1176,7 @@ void main() { }); test( - 'step change notification fires for non-first transitions only once', + 'legacy Tick does not invent elapsed progress or replay notifications', () async { final schedule = buildSchedule( id: 'notify', @@ -1200,11 +1203,11 @@ void main() { expect(notifiedStepIds, isEmpty); bloc.add(const ScheduleTick(Duration(minutes: 10))); await Future.delayed(Duration.zero); - expect(notifiedStepIds, ['s2']); + expect(notifiedStepIds, isEmpty); bloc.add(const ScheduleTick(Duration(minutes: 1))); await Future.delayed(Duration.zero); - expect(notifiedStepIds, ['s2']); + expect(notifiedStepIds, isEmpty); }, ); From e6c1438ebcd641386081bb5aa5569e1b7da7ee15 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:00:45 +0900 Subject: [PATCH 39/59] docs: record verified timer delivery and reset recovery CI --- handoff/audit-2026-09-23-progress.md | 4 + plans/audit-2026-09-23/README.md | 8 +- .../a06-final-validation.json | 58 ++ .../artifacts/a06/a06-analyze-final.log | 2 + .../artifacts/a06/a06-boundary-final.log | 49 + .../artifacts/a06/a06-coverage-policy.log | 1 + .../artifacts/a06/a06-focused-v2.log | 105 ++ .../artifacts/a06/a06-full-coverage.log | 938 ++++++++++++++++++ .../artifacts/a06/a06-generated-policy.log | 1 + .../artifacts/a06/a06-local-only-policy.log | 1 + .../artifacts/a06/a06-manifest-policy.log | 1 + .../artifacts/a06/a06-negative.log | 31 + .../artifacts/a06/a06-python-policies.log | 5 + .../artifacts/a06/source-freeze.json | 9 + .../d03/integrated/d03-merge-targeted.log | 2 +- plans/audit-2026-09-23/d03-ci-validation.json | 42 + .../d03-main-integration-validation.json | 6 +- plans/audit-2026-09-23/issues/A06.md | 17 + plans/audit-2026-09-23/issues/D03.md | 2 + plans/audit-2026-09-23/issues/U03.md | 194 ++++ plans/audit-2026-09-23/state.json | 55 +- 21 files changed, 1505 insertions(+), 26 deletions(-) create mode 100644 plans/audit-2026-09-23/a06-final-validation.json create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-analyze-final.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-boundary-final.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-coverage-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-focused-v2.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-full-coverage.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-generated-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-local-only-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-manifest-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-negative.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/a06-python-policies.log create mode 100644 plans/audit-2026-09-23/artifacts/a06/source-freeze.json create mode 100644 plans/audit-2026-09-23/d03-ci-validation.json create mode 100644 plans/audit-2026-09-23/issues/U03.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 3b3b99e7..2e8e1d2f 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -89,3 +89,7 @@ D03 원래 전담의 P2 구현과 root 연결이 완료됐다. 전체872/집중7 ## 최신 main 통합 및 다음 작업 main c7184b2a Figma PR607 충돌을659a5fc4에서해결,복구operation인자오류074d5fd6수정. 관련36tests+실제UIcapture2통과,root3화면직접확인. 원격074d5fd6 CI확인중. D03상세이슈최신본문은아직게시대기. C08#610/U01#611 게시검증하여25/64,39개생성전. U01CONTEXT/ADR0034 독점/공유원문삭제정책root반영. A06전담구현agent가Flutter소유권가지고focused/analyze/full준비중,root제품파일수정금지. U03전담grill문답중. + +## D03 원격 검증 완료 및 A06 전달 + +D03최종main통합074d5fd6 원격Flutter882/analyze/88.50%,AndroidAPK/manifest통과 artifact10786008849(identity d03-ci-validation.json). A06d06474d4 실제periodic수정commit,47boundary/929full/88.74%,7blob검증완료(a06-final-validation.json),#598본문원격대조완료. U03#612 실제3문답+28case게시26/64,38개생성전. T03전담grill실제3문답확정문서작성중. A07전담구현시작,Flutter명령소유권A07. root는A06docs/push/CI후상위우선구현계속. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 61bbf579..83d1a34a 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -26,8 +26,8 @@ | A13 | [#592](https://github.com/DevKor-github/OnTime-front/issues/592) | 예약/안내·registry 후속 수정 원격740 tests/87.20%·APK 통과, 기기 대기 | | A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측 구현282db21f, 원격772 tests/87.44%·APK 통과, A15 실제 ownership 후속 로컬795 통과·기기 대기 | | A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 요청별 후속 재조정/교체 직렬화 구현, 최종 원격795 tests/analyze·87.66%·APK 통과, 기기 대기 | -| D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 독립 journal·초기화 복구 구현, 최종872 tests/87.99%·host kill11 통과, CI·기기 대기 | -| A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | 실제3문답·정정 포함, 신선한 실제 타이머 전환 알림 기준 확정 | +| D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 독립 journal·복구/host kill11, main통합 원격882 tests/88.50%·APK 통과,기기 대기 | +| A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | d06474d4 실제 타이머 전환 구현,47경계/929전체·88.74% 통과,CI·기기 대기 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | 실제3문답·추가 정정 포함, DB 원자적 시작·run 시각·실패 재시도 기준 확정 | | C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 실제3문답·staging/gate 보충 포함, 구체 workflow·부분 결과·통합 의존성 확정 | | A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 실제4문답·원자 저장/조회·충돌/멱등/소유권 기준 확정 | @@ -45,7 +45,9 @@ | U01 | [#611](https://github.com/DevKor-github/OnTime-front/issues/611) | 실제3문답·이력 단일삭제/공유원문·점수보존/취소ownership,32개 검증 시나리오 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **25/64개**를 게시하고 원격 본문 동일성을 확인했으며 **39개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 후 최종872 tests/87.99% 및 host kill11 검증을 통과했고 CI·기기 확인을 진행한다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +| U03 | [#612](https://github.com/DevKor-github/OnTime-front/issues/612) | 실제3문답·최신 토글의도/저장-OS반영 구분/화면이탈·세대 경계,28개 검증 시나리오 확정 | + +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **26/64개**를 게시하고 원격 본문 동일성을 확인했으며 **38개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 후 최종872 tests/87.99% 및 host kill11 검증을 통과했고 CI·기기 확인을 진행한다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/a06-final-validation.json b/plans/audit-2026-09-23/a06-final-validation.json new file mode 100644 index 00000000..85e2bdeb --- /dev/null +++ b/plans/audit-2026-09-23/a06-final-validation.json @@ -0,0 +1,58 @@ +{ + "issue": "A06 #598", + "source_files": { + "lib/core/services/notification_service.dart": "a91d7c029f22ad61eb9bf540d884a29d0879010747d268a085c6ab0cde080fab", + "lib/presentation/app/bloc/schedule/schedule_bloc.dart": "5a15dc1e8c7918ce1759f5df021c9bebd0629be6130bd7d61a91da3f2449efe0", + "lib/presentation/app/bloc/schedule/schedule_event.dart": "c495cca0d838643a2bb6f0f1110d8fdc8cb838404feaa1c8866d5c62ba0be936", + "lib/presentation/app/screens/app.dart": "0fbd83a8eb8227809d02e8072f68325ee1578d8b39814453e41428e812c1a837", + "test/presentation/app/bloc/schedule/schedule_bloc_test.dart": "bc715a45d509e3203d7621efbbb6066b25f10ae1e198f4cab1d96d64f2d50a08", + "test/core/services/preparation_step_notification_test.dart": "5a9d45fe439c38ee00471e90fa849e7d9be65160e5f898aaa4bc1696679dcea7", + "test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart": "ad03928eeb7e1c4b55054b1cbf301d18104e8dd7add28504a062cf5ab6d532ae" + }, + "results": { + "full_tests": 929, + "new_boundary_tests": 47, + "coverage_percent": 88.74, + "covered_lines": 13092, + "total_lines": 14753, + "analyzer": "No issues found", + "python_policy_tests": 11, + "generated_policy": "pass", + "local_only_policy": "pass", + "android_source_manifest": "pass", + "diff_check": "pass" + }, + "limitations": [ + "Android/iOS device background, lock, resume, display permission QA not executed", + "Plugin show completion is submission, not proof of visible OS notification", + "No guarantee of periodic execution while suspended or killed", + "No native APK or iOS build executed for this change" + ], + "implementation_commit": "d06474d4c645794644075173f76afb9ff4445047", + "logs_raw_sha256": { + "/tmp/a06-negative.log": "28bb4e09b59e6d2047ad93491c8e4fe41519024bc798fe551d5133aff4d0dd11", + "/tmp/a06-focused-v2.log": "d1534a3b5187d3eff5acf28433f26b5a07e39e7dd32c3c9ab8b9317cfe2c0879", + "/tmp/a06-boundary-final.log": "4ba6ae959e6852694921350d483a500a8e5b8373467af67a37320efb7a88f8c0", + "/tmp/a06-analyze-final.log": "108b7d56681eac78775e3c9378837f1f8e0412b37b2abd10ec1bcaee0235f632", + "/tmp/a06-full-coverage.log": "129513993c09666846e3a396a13ffd6cc0240cbceb86a8be707479ac225f5a8b", + "/tmp/a06-coverage-policy.log": "aa77fcd4fe1820edf645a85420bbd7b24c50fbec5316eb33a5c7093e882c21f7", + "/tmp/a06-generated-policy.log": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "/tmp/a06-local-only-policy.log": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "/tmp/a06-python-policies.log": "1d83585b97486319bb9c2fff2996c7435fd78e26e63e611aae2ad6791d35db6b", + "/tmp/a06-manifest-policy.log": "60ac45f9be8ec739ed709fa43c33c8f51a94665b6bdea633dd65e27529d13c01" + }, + "artifacts": { + "plans/audit-2026-09-23/artifacts/a06/a06-negative.log": "9c091616c38249759858c4227cb65043f8d02b486b32425576757849a6b38ad7", + "plans/audit-2026-09-23/artifacts/a06/a06-focused-v2.log": "d1534a3b5187d3eff5acf28433f26b5a07e39e7dd32c3c9ab8b9317cfe2c0879", + "plans/audit-2026-09-23/artifacts/a06/a06-boundary-final.log": "4ba6ae959e6852694921350d483a500a8e5b8373467af67a37320efb7a88f8c0", + "plans/audit-2026-09-23/artifacts/a06/a06-analyze-final.log": "373e11882e69290552a80ca5ac78af50eb68629e11ed821e0d9a2536ced71af3", + "plans/audit-2026-09-23/artifacts/a06/a06-full-coverage.log": "129513993c09666846e3a396a13ffd6cc0240cbceb86a8be707479ac225f5a8b", + "plans/audit-2026-09-23/artifacts/a06/a06-coverage-policy.log": "aa77fcd4fe1820edf645a85420bbd7b24c50fbec5316eb33a5c7093e882c21f7", + "plans/audit-2026-09-23/artifacts/a06/a06-generated-policy.log": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "plans/audit-2026-09-23/artifacts/a06/a06-local-only-policy.log": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "plans/audit-2026-09-23/artifacts/a06/a06-python-policies.log": "1d83585b97486319bb9c2fff2996c7435fd78e26e63e611aae2ad6791d35db6b", + "plans/audit-2026-09-23/artifacts/a06/a06-manifest-policy.log": "60ac45f9be8ec739ed709fa43c33c8f51a94665b6bdea633dd65e27529d13c01", + "plans/audit-2026-09-23/artifacts/a06/source-freeze.json": "ee48302e08f59878680f738635d6532fa898ac6a9961b90df297a6fc8de15ca1" + }, + "log_normalization": "strip trailing whitespace in repo copies; raw log hashes retained" +} diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-analyze-final.log b/plans/audit-2026-09-23/artifacts/a06/a06-analyze-final.log new file mode 100644 index 00000000..0297964e --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-analyze-final.log @@ -0,0 +1,2 @@ +Analyzing OnTime-front... +No issues found! (ran in 1.4s) diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-boundary-final.log b/plans/audit-2026-09-23/artifacts/a06/a06-boundary-final.log new file mode 100644 index 00000000..5d8a44fa --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-boundary-final.log @@ -0,0 +1,49 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: AppLifecycleState.detached updates UI silently without later replay +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=true retains private text/minimum payload while upcoming OFF +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: null updates UI silently without later replay +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: en detailed=false retains private text/minimum payload while upcoming OFF +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: freshness boundary 0:00:02.000000 +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: freshness boundary 0:00:02.000000 +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: display denied skips show without permission prompt +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: freshness boundary 0:00:02.000001 +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: permission query error propagates to the observed caller, no show +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: wall/monotonic tolerance boundary 0:00:00.250000 +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: AppLifecycleState.resumed never queries or submits +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: wall/monotonic tolerance boundary 0:00:00.250001 +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: AppLifecycleState.inactive never queries or submits +00:00 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: phase roundtrip between observations resets continuity +00:00 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: AppLifecycleState.hidden never queries or submits +00:00 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: clock jump 0:00:03.000000 does not submit stale step +00:00 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: clock jump 0:00:03.000000 does not submit stale step +00:00 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: permission await rechecks session before show +00:00 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: clock jump -0:00:01.000000 does not submit stale step +00:00 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: permission await rechecks resume before show +00:00 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: clock jump 0:00:07.000000 does not submit stale step +00:00 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: permission await rechecks replacement before show +00:00 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: resume catch-up and legacy Tick cannot replay or double count +00:00 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: setup await rechecks validity immediately before plugin submission +00:00 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: restore is silent then future adjacent boundary remains eligible +00:00 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: show failure propagates without releasing owner early or unhandled error +00:00 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: skip is silent, next automatic transition is independent +00:00 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: reset cleanup waits for actual in-flight plugin effect, then cancels +00:00 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: first and final transitions produce no notification +00:00 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: queued old-generation step is rejected before any platform call +00:00 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: delivery sync throw consumes candidate but next step proceeds +00:00 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: delivery Future error consumes candidate but next step proceeds +00:00 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: await validity rejects resume +00:00 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: await validity rejects prompt +00:00 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: await validity rejects delete +00:00 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: await validity rejects finish +00:00 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: await validity rejects generation +00:00 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: await validity rejects stale +00:00 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: same occurrence new run resets dedupe, restore does not replay +00:00 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: replacement generation with reused run and step IDs has independent attempts +00:00 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: occurrences sharing step IDs have independent attempts +00:00 +47: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-coverage-policy.log b/plans/audit-2026-09-23/artifacts/a06/a06-coverage-policy.log new file mode 100644 index 00000000..8e1dc67a --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-coverage-policy.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Coverage: 88.74% (13092/14753 lines) diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-focused-v2.log b/plans/audit-2026-09-23/artifacts/a06/a06-focused-v2.log new file mode 100644 index 00000000..6427ae05 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-focused-v2.log @@ -0,0 +1,105 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:00 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: resume catch-up and legacy Tick cannot replay or double count +00:00 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:00 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow early start from upcoming emits started and marks early session +00:00 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm launch start action keeps cached schedule on start prompt +00:00 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm launch start action keeps stale-fingerprint cached schedule on start prompt +00:00 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:00 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:00 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:00 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:00 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:01 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:02 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow same schedule re-emission preserves started state when early-started +00:02 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish clears timed preparation and early session state +00:02 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow confirmation-required run stays stopped after prompt until explicit start +00:02 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow mismatched fingerprint invalidates cached progress +00:02 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start when early session and snapshot exist +00:02 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start with only early session derives elapsed from session start +00:02 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt loads schedule details and arms the prompt +00:02 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects ended schedules and clears their alarm +00:02 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects stale fingerprint unless it starts prep +00:02 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm prompt validation failure clears only non-start prompts +00:02 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish failure keeps the active schedule visible +00:02 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: schedule events expose equality props for bloc deduping +00:02 +103: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-full-coverage.log b/plans/audit-2026-09-23/artifacts/a06/a06-full-coverage.log new file mode 100644 index 00000000..775cd888 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-full-coverage.log @@ -0,0 +1,938 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:01 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:03 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:04 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:04 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:04 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: only complete iOS OS observations resolve reconstructed uncertainty +00:04 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeUnmapped remains partial without guessing ownership +00:04 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeUnmapped remains partial without guessing ownership +00:04 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeUnmapped remains partial without guessing ownership +00:04 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: failure before Auth attempts cleanup and preserves original failure +00:04 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeError remains partial without guessing ownership +00:04 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeError remains partial without guessing ownership +00:04 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS fallbackError remains partial without guessing ownership +00:04 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS unownedNotification remains partial without guessing ownership +00:04 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: secure reset marker resumes deletion but Android uncertainty remains +00:04 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:05 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:05 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:05 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:05 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:05 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:05 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: normal civil time resolves to one absolute occurrence +00:05 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: normal civil time resolves to one absolute occurrence +00:05 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:05 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:05 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:05 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:05 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at verified recovers through a new owner +00:05 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at renamed recovers through a new owner +00:05 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at readBack recovers through a new owner +00:05 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryVerified recovers through a new owner +00:05 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:06 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:06 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: preserves case and surrounding spaces +00:06 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: rejects passwords outside the 15 to 128 code point boundary +00:06 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: sends encrypted bytes and name only; saved receipt has no location +00:06 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: cancel is distinct from success +00:06 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: unrecognized native response null is never success +00:06 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: unrecognized native response is never success +00:06 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: unrecognized native response unknown is never success +00:06 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:07 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:07 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:07 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:07 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:07 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:07 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:07 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:08 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:08 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:08 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:08 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:08 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:08 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:08 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:08 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:08 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:08 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:09 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:09 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:09 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:09 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:10 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:12 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/fallback_alarm_notification_service_test.dart: permission checks map local notification authorization to alarm permission +00:12 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/fallback_alarm_notification_service_test.dart: not-determined notification status remains recoverable +00:12 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/fallback_alarm_notification_service_test.dart: schedules and cancels fallback alarms through notification service +00:13 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: independent file retains only cancellation identity across reopen +00:13 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at written reopens a complete committed generation +00:13 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at verified reopens a complete committed generation +00:13 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:13 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at readBack reopens a complete committed generation +00:13 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: staged first write is preserved and never reported as an empty installation +00:13 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: truncated staged first write is preserved and blocks new ownership +00:13 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {broken +00:13 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {"version":2} +00:13 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {"version":1,"ownership":[]} +00:13 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: backup exclusion failure prevents creating ownership bytes +00:13 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: only a fully completed reset journal can be removed +00:13 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: duplicate actual platform IDs are rejected instead of hiding ownership +00:13 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: impossible completion states cannot be written over a valid journal +00:14 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: initializeLaunchHandling dispatches cold-start alarm payload +00:14 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload dispatches resumed alarm payload +00:14 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities maps native alarm provider capabilities +00:14 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats missing platform implementation as unsupported +00:14 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats native platform errors as unsupported +00:14 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats null native response as unsupported +00:14 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: checkPermission and requestPermission map native wire values +00:14 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat missing platform implementation as unsupported +00:14 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat native platform errors as unsupported +00:14 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm sends the native alarm contract payload +00:14 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm exposes permission failures as scheduling exception +00:14 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm maps unsupported and generic native errors +00:14 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm sends cancellation payload for scheduled records +00:14 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm reports missing native platform as unconfirmed +00:14 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm maps native platform failures to scheduling exception +00:14 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelAllNativeAlarms cancels every provided record in order +00:14 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload ignores invalid and failed payload reads +00:14 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=false retains private text/minimum payload while upcoming OFF +00:14 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: ko detailed=true retains private text/minimum payload while upcoming OFF +00:14 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: en detailed=false retains private text/minimum payload while upcoming OFF +00:14 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: en detailed=true retains private text/minimum payload while upcoming OFF +00:14 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: display denied skips show without permission prompt +00:14 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: permission query error propagates to the observed caller, no show +00:14 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: AppLifecycleState.resumed never queries or submits +00:14 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: AppLifecycleState.inactive never queries or submits +00:14 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: AppLifecycleState.hidden never queries or submits +00:14 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: AppLifecycleState.detached never queries or submits +00:14 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: permission await rechecks session before show +00:14 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: permission await rechecks resume before show +00:14 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: permission await rechecks replacement before show +00:14 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: setup await rechecks validity immediately before plugin submission +00:14 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: show failure propagates without releasing owner early or unhandled error +00:14 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: reset cleanup waits for actual in-flight plugin effect, then cancels +00:14 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/preparation_step_notification_test.dart: queued old-generation step is rejected before any platform call +00:15 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=false snapshot after persistence +00:15 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=true snapshot after persistence +00:15 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=false snapshot after persistence +00:15 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=true snapshot after persistence +00:15 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: legacy and corrupted content cannot bypass reconciliation at either provider +00:15 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancel uses the plugin API for pending and presented IDs, never cancelAll +00:15 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancellation errors are visible to reconciliation +00:15 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/app_metadata_service_test.dart: package info metadata provider returns runtime app metadata +00:16 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: owned legacy pending is cancelled without clearing step notifications +00:16 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: cancel failure preserves only ownership tombstone and retry converges +00:16 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:16 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: successful cancel call without readback removal is not success +00:16 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: cold launch collects once and concurrent initialization is single flight +00:16 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: null +00:16 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:16 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:16 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: getter failure releases single flight and callback still works before retry +00:16 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback B arriving while cold A getter waits wins, raw legacy fields removed +00:16 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback before delegate installation preserves only safe latest payload +00:16 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: Noop to installed delegate cannot let slow cold A overwrite native B +00:16 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: data replacement invalidates delayed initial launch +00:16 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:16 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android read failure is unknown, not absent +00:16 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cancel needs public success and post read-back +00:16 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache absence does not hide cancel failure +00:16 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android remaining ID and failed post query are unconfirmed cancellations +00:16 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS OS observation preserves source and minimal ownership +00:16 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS read failure is unknown, not absent +00:16 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cancel needs public success and post read-back +00:16 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cache absence does not hide cancel failure +00:16 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS remaining ID and failed post query are unconfirmed cancellations +00:16 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit observation rejects unavailable, malformed and unrelated IDs +00:16 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit already absent is idempotent; present cancel requires fresh absence +00:16 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:16 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: platform cleanup failure is visible and retryable, without raw content +00:16 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: DB-unavailable cleanup removes legacy raw and does not touch unrelated data +00:17 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: localizedNotificationText selects Korean text only for Korean locale +00:17 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: isScheduleAlarmPayload detects native alarm payloads without suppressing schedule notifications +00:17 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule notification payload to the schedule start screen +00:17 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes five-minute prompts as early-start schedule starts +00:17 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule and preparation updates to the alarm screen +00:17 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString ignores null, invalid, and unrelated payloads +00:17 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: invalid schedule hints never route to nearest preparation +00:17 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForData routes decoded local payload data with the same rules +00:17 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:17 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: explicit exact denial retries once and returns actual approximate receipt +00:17 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unrelated provider error is never converted to approximate success +00:17 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: failed approximate retry stays failed and is not retried again +00:17 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unknown timing permission uses approximate rather than claiming exact +00:17 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: display denial blocks scheduling even with exact access +00:17 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: settings response is not proof of a grant: permission is reread +00:17 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: iOS timing access is unsupported without calling Android plugin +00:17 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:17 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:17 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:18 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real storage retains every failed platform ownership for the same Schedule +00:18 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:18 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=true +00:18 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: loadAll returns the current local registry +00:18 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:18 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:18 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:18 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:18 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:18 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:18 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:18 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:18 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:18 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk changes preserve only overridden fields and preparation +00:18 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: ending following leaves active and historical rows and their preparations intact +00:18 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: finite conflicts require explicit exclusion and excluded slots consume count +00:18 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: place edits never rename another occurrence or historical place +00:18 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk edit preserves active preparation and consumes its original count +00:18 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: stale first occurrence approval asks for review without changing durable data +00:18 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:18 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:18 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:18 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:19 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:19 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:19 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:19 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:19 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: spare time preserves preferences, scores and export metadata +00:19 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: repeated initial creation cannot replace an existing profile +00:19 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: scrubbing irrecoverable identities retains a content-free uncertainty marker +00:19 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: scrubbing irrecoverable identities retains a content-free uncertainty marker +00:19 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:19 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:19 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:19 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:19 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: malformed new content metadata never drops registry ownership records +00:19 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: malformed new content metadata never drops registry ownership records +00:19 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:19 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:19 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:19 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:19 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:19 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: preferences round trip preserves NotificationTiming.exact and ownership pending=true +00:19 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:19 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:19 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:19 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:19 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: multiple preparation steps are a no-op even when caller omits links +00:19 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: missing profile rejects updates and never reports a successful mutation +00:19 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: empty profile starts without a durable revision and creation is race safe +00:19 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: raw revision update notifies DB watchers and failed profile write publishes no success +00:19 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:19 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:19 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedule date ranges as start-inclusive and end-exclusive +00:19 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:20 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_data_source_contract_test.dart: schedule local data source contract does not expose domain entities +00:20 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:20 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:20 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:20 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:20 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:20 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:20 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:20 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:20 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:20 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:20 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:20 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:20 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:21 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:21 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:21 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:21 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:21 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:21 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getUserById returns null for an unknown user +00:21 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getAllUsers returns all persisted users as domain entities +00:22 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:22 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:22 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:22 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:22 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns each step once when links form a cycle +00:22 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return an empty preparation list when user has no steps +00:22 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById returns the stored step contract +00:22 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:22 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:22 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps when rows are out of order +00:22 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:22 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should keep remaining preparation steps when a link is broken +00:22 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:22 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return each preparation step once when links form a cycle +00:22 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should return one preparation step by id +00:22 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:22 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:22 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: updatePreparationUser should update a preparation step +00:22 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:22 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: deleting a counted slot never extends or fills the series +00:22 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: last day is distinct from 31st, including leap February +00:22 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fifth weekday skips missing months while last weekday does not +00:22 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: weekly matching starts inclusively without adding the anchor day +00:22 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: daily intervals keep civil time and include the end date +00:22 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: spring gap is skipped and does not consume the count +00:22 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fall overlap requires an explicit first or second choice +00:22 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: new series starts at a future preparation and counts from there +00:22 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: range queries preserve original count and stable slot keys +00:22 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: unknown zone and exhausted search never report success +00:22 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: invalid and ambiguous rule definitions are rejected +00:23 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:23 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:23 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:23 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:23 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:23 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:23 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation +00:23 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:23 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:23 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: skips a just-missed alarm instead of scheduling catch-up +00:23 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:23 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:23 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:24 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:24 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:24 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:24 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:24 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:24 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:24 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:24 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:24 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:24 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:24 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:24 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:24 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:24 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:24 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:24 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:24 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:24 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:24 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:24 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:24 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:24 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:24 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:24 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:24 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:24 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:24 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:24 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:24 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:24 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:24 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:24 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:24 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:24 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:24 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:24 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:24 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:24 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:24 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:24 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:24 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:24 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:24 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:24 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:24 +406: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart +00:25 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:25 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure does not undo committed start or skip its snapshot +00:25 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure after finish still clears completed runtime +00:25 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:25 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:25 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:25 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:25 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:25 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:25 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:25 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:25 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:25 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:26 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:26 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:26 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/create_schedule_form_submission_use_case_test.dart: changed preparation creates schedule and saves custom preparation locally +00:26 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:26 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: a previous civil date in a negative offset still supplies a future alarm +00:26 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart: unchanged preparation updates the schedule without saving preparation +00:26 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart: changed default preparation is copied with fresh ordered step IDs +00:26 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart: changed custom preparation preserves step IDs +00:27 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: each cutoff finishes without waiting for later edits to become quiet +00:27 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failed snapshot completes its cutoff and still serves a newer request +00:27 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failure alone stops; a request from completion starts another pass +00:27 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: replacement waits for the actual late native Future before same-ID reuse +00:27 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: caller timeout cannot release ownership of an unreturned native Future +00:27 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: ordinary export busy does not invalidate a requested pass +00:27 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: replacement cancellation failure retains evidence and prevents replacement +00:27 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: delete mutation accepted during snapshot cannot be resurrected by replaceAll +00:27 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:27 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:27 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:27 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:27 +453: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart +00:27 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=false +00:27 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:27 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:27 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: a registry-owned ID with a conflicting route is cancelled before repair +00:27 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=true +00:27 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=true +00:27 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=false +00:27 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=false +00:27 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native revoke and grant switch providers after cancelling the old registration +00:27 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:27 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:27 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:27 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:27 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:27 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:27 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:27 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: confirmed fallback orphans are targeted; unrelated and unknown ownership survive +00:27 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:27 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native failure uses only the actual fallback receipt after native cleanup +00:27 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed native write cleanup forbids fallback and keeps cancellation identity +00:27 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:27 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: off retains a cleanup warning and denial never becomes precise +00:27 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: no future registrations never becomes precise from capability +00:27 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: iOS alarm and notification language remains capability appropriate +00:28 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:28 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: empty user exposes null convenience values +00:28 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:28 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:28 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:28 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:28 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:28 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:28 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities calculates left time before leave with explicit now +00:28 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports late status after leave time has passed +00:28 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities fast-forwards preparation steps for late entry +00:28 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities single preparation step tracks elapsed time and diagnostics +00:28 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities marks all steps done for very late entry inside schedule window +00:28 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports display helpers for current and completed steps +00:28 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:28 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:28 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:28 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:28 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:28 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:28 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:28 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:28 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:28 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:28 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:28 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: minimal progress hydrates names/durations from current source only +00:28 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy exact fingerprint additionally requires all step fields +00:28 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy launch becomes a minimal versioned route, never a start command +00:28 +505: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart +00:29 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:29 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:29 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:29 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:29 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:30 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (setUpAll) +00:30 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:30 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:31 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:31 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:31 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:31 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:31 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:31 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:31 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: scheduled days render without hiding the selected day +00:31 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:31 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:31 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: header arrows move between months and request month loads +00:31 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:31 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:32 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits without scroll at 1.3 text scale +00:32 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: HomeScreenTmp subscribes monthly bloc for today +00:32 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits when today schedule exists +00:32 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +549: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +550: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +551: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +552: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +553: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +554: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +555: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +556: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +557: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +558: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +559: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +560: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +561: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +562: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +563: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:32 +564: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:33 +565: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +566: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +568: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +569: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +570: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +571: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +572: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +573: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +574: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +576: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +577: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +578: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +579: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +580: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:33 +581: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: tile text updates when schedule fields change for same id +00:33 +582: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:33 +583: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:33 +584: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden after preparation start time +00:33 +585: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:33 +586: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:33 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:33 +588: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:34 +589: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:34 +590: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:34 +591: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:34 +592: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:34 +593: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:34 +594: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:34 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:34 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:34 +597: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:34 +598: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +599: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +600: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +601: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +602: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +603: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +604: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +605: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +606: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +607: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +608: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:34 +609: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: phase roundtrip between observations resets continuity +00:34 +610: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:34 +611: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:34 +612: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:34 +613: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:34 +614: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:34 +615: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:34 +616: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:34 +617: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:35 +618: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:35 +619: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:35 +620: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:35 +621: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:35 +622: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:35 +623: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:35 +624: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:35 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow emits notExists when there is no upcoming schedule +00:35 +626: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow emits notExists when there is no upcoming schedule +00:35 +627: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow emits notExists when there is no upcoming schedule +00:35 +628: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:35 +629: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:35 +630: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:35 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:35 +632: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:35 +633: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:35 +634: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:35 +635: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:35 +636: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:35 +637: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +638: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +639: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +640: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +641: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +642: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +643: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +644: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +645: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +646: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +647: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +648: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +649: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +650: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +651: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +652: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +653: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +654: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:35 +655: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:35 +656: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:35 +657: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:35 +658: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow early start from upcoming emits started and marks early session +00:35 +659: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm launch start action keeps cached schedule on start prompt +00:35 +660: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm launch start action keeps stale-fingerprint cached schedule on start prompt +00:35 +661: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:35 +662: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:35 +663: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:36 +664: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:36 +665: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:36 +666: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:36 +667: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:36 +668: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:36 +669: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:36 +670: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:36 +671: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:36 +672: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:37 +673: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:37 +674: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:37 +675: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:37 +676: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow same schedule re-emission preserves started state when early-started +00:37 +677: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish clears timed preparation and early session state +00:37 +678: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow confirmation-required run stays stopped after prompt until explicit start +00:37 +679: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow mismatched fingerprint invalidates cached progress +00:37 +680: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start when early session and snapshot exist +00:37 +681: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start with only early session derives elapsed from session start +00:37 +682: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt loads schedule details and arms the prompt +00:37 +683: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects ended schedules and clears their alarm +00:37 +684: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects stale fingerprint unless it starts prep +00:37 +685: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm prompt validation failure clears only non-start prompts +00:37 +686: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish failure keeps the active schedule visible +00:37 +687: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: schedule events expose equality props for bloc deduping +00:38 +688: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:38 +689: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default marker when a scheduled day is not selected +00:38 +690: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default empty-day behavior when there are no events +00:38 +691: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:38 +692: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:38 +693: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:38 +694: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:38 +695: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:38 +696: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:38 +697: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:39 +698: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:39 +699: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:39 +700: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:39 +701: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:39 +702: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: flex layout works in row without overflow +00:39 +703: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:39 +704: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:39 +705: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:39 +706: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: date picker saves the initial date value +00:39 +707: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart +00:40 +707: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (setUpAll) +00:40 +707: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: button styles resolve enabled and disabled visual contracts +00:40 +708: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme builds app calendar tokens and copies overrides +00:40 +709: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/tile_style_test.dart: copyWith keeps existing tile style values when not overridden +00:40 +710: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:40 +711: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:40 +712: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:41 +713: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:41 +714: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:41 +715: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (tearDownAll) +00:41 +715: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/route_arguments_test.dart: calendar route arguments prefers DateTime extra and falls back to query parameters +00:41 +716: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/route_arguments_test.dart: calendar route arguments router does not throw for malformed calendar extras +00:41 +717: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +718: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +719: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +720: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +721: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +722: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +723: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +724: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +725: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +726: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +727: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +728: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:41 +729: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:42 +730: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:42 +731: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:42 +732: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:43 +733: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:43 +734: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:43 +735: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:43 +736: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:43 +737: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:43 +738: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:43 +739: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:43 +740: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:44 +741: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:44 +742: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:44 +743: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:44 +744: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:44 +745: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:44 +746: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:44 +747: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:44 +748: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:44 +749: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:44 +750: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:44 +751: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:44 +752: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:44 +753: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:45 +754: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:45 +755: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +756: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +757: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +758: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +759: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +760: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +761: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +762: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +763: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +764: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +765: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +766: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +767: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +768: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +769: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +770: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:45 +771: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:46 +772: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:46 +773: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:46 +774: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:46 +775: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: new notification grant initializes local notifications +00:46 +776: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:46 +777: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:46 +778: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:46 +779: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:46 +780: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:46 +781: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:46 +782: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:46 +783: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:47 +784: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:47 +785: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:47 +786: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:47 +787: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets alarm prompt without cached schedule still offers early start +00:47 +788: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets close button confirms before leaving start prompt +00:47 +789: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets early-start screen variant is shown with two choices +00:48 +790: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +791: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +792: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +793: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +794: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +795: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +796: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +797: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +798: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +799: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +800: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +801: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +802: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +803: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +804: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +805: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:48 +806: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:48 +807: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:48 +808: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:48 +809: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:48 +810: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:48 +811: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:48 +812: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:48 +813: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:48 +814: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue shows live leave countdown for early-start flow +00:48 +815: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:49 +816: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue keeps overdue timer running after leave time +00:49 +817: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: uses alarm language for iOS AlarmKit capability +00:49 +818: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog finish triggers finish flow +00:49 +819: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog finish triggers finish flow +00:49 +820: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets already missing schedule on alarm entry navigates home +00:49 +821: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets null schedule emission while already notExists navigates home +00:49 +822: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets stale notification after schedule end should redirect safely (spec-first) +00:49 +823: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:50 +824: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: selected preparation and timing are submitted as the Local Profile setup +00:50 +825: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:51 +826: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:51 +827: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:51 +828: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:51 +829: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:51 +830: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:51 +831: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:51 +832: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:52 +833: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:52 +834: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:52 +835: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:52 +836: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:52 +837: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:52 +838: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:52 +839: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:52 +840: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:52 +841: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:52 +842: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:52 +843: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: scope defaults to only this occurrence and supports following scope +00:52 +844: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:53 +845: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: failed reset remains recoverable and never reports success +00:53 +846: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: partial reset opens recovery instead of reporting completion +00:53 +847: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +848: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +849: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +850: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +851: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +852: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +853: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +854: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +855: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +856: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +857: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +858: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +859: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +860: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +861: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +862: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +863: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +864: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +865: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +866: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +867: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +868: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +869: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:53 +870: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:54 +871: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_time_inputs_cubit_test.dart: place and moving time cubit validates fields and submits to form bloc +00:54 +872: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:54 +873: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:54 +874: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:54 +875: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:54 +876: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:54 +877: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: fills the bottom safe area while keyboard is hidden +00:55 +878: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: paints a white backplate behind the iOS keyboard inset +00:55 +879: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: does not add keyboard backplate while keyboard is hidden +00:55 +880: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:55 +881: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:55 +882: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +883: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +884: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +885: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +886: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +887: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +888: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +889: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +890: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +891: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +892: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +893: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:56 +894: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: saves date and time picker selections through the cubit +00:56 +895: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:56 +896: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:56 +897: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: shows explicit choices for a repeated DST time +00:56 +898: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:56 +899: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:57 +900: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:57 +901: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:57 +902: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:57 +903: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:57 +904: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:58 +905: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:58 +906: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:58 +907: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:58 +908: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:58 +909: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:58 +910: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:58 +911: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_create_list_test.dart: create list forwards edits, reorder changes, and create request +00:58 +912: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:59 +913: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +914: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +915: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +916: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +917: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +918: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +919: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +920: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +921: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +922: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:59 +923: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +01:00 +924: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +01:00 +925: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation errors explain invalid name and preparation time +01:00 +926: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +01:00 +927: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +01:00 +928: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +01:00 +929: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-generated-policy.log b/plans/audit-2026-09-23/artifacts/a06/a06-generated-policy.log new file mode 100644 index 00000000..01eef09f --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-generated-policy.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Generated Dart policy is aligned: outputs are ignored and untracked. diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-local-only-policy.log b/plans/audit-2026-09-23/artifacts/a06/a06-local-only-policy.log new file mode 100644 index 00000000..71a9ce0a --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-local-only-policy.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Local-only product boundary verified. diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-manifest-policy.log b/plans/audit-2026-09-23/artifacts/a06/a06-manifest-policy.log new file mode 100644 index 00000000..29c18cf1 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-manifest-policy.log @@ -0,0 +1 @@ +{"manifest": "android/app/src/main/AndroidManifest.xml", "sha256": "6b16f0fbaf8febcc2f9647450e40ca498462e2142f279c90666f86f77f14f210", "failures": []} diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-negative.log b/plans/audit-2026-09-23/artifacts/a06/a06-negative.log new file mode 100644 index 00000000..19e70a1e --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-negative.log @@ -0,0 +1,31 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart +00:00 +0: ScheduleBloc preparation runtime flow actual periodic refresh submits a fresh paused step once +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +The following TestFailure was thrown running a test: +Expected: ['s2'] + Actual: [] + Which: at location [0] is [] which shorter than expected + +When the exception was thrown, this was the stack: +#4 main.. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart:618:7) + +#5 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:15) + +#6 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1952:5) + + +(elided one frame from package:stack_trace) + +This was caught by the test expectation on the following line: + file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart line 618 +The test description was: + actual periodic refresh submits a fresh paused step once +════════════════════════════════════════════════════════════════════════════════════════════════════ +00:00 +0 -1: ScheduleBloc preparation runtime flow actual periodic refresh submits a fresh paused step once [E] + Test failed. See exception logs above. + The test description was: actual periodic refresh submits a fresh paused step once + +00:00 +0 -1: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow actual periodic refresh submits a fresh paused step once diff --git a/plans/audit-2026-09-23/artifacts/a06/a06-python-policies.log b/plans/audit-2026-09-23/artifacts/a06/a06-python-policies.log new file mode 100644 index 00000000..e81ac111 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/a06-python-policies.log @@ -0,0 +1,5 @@ +........... +---------------------------------------------------------------------- +Ran 11 tests in 0.010s + +OK diff --git a/plans/audit-2026-09-23/artifacts/a06/source-freeze.json b/plans/audit-2026-09-23/artifacts/a06/source-freeze.json new file mode 100644 index 00000000..53c840c1 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a06/source-freeze.json @@ -0,0 +1,9 @@ +{ + "lib/core/services/notification_service.dart": "a91d7c029f22ad61eb9bf540d884a29d0879010747d268a085c6ab0cde080fab", + "lib/presentation/app/bloc/schedule/schedule_bloc.dart": "5a15dc1e8c7918ce1759f5df021c9bebd0629be6130bd7d61a91da3f2449efe0", + "lib/presentation/app/bloc/schedule/schedule_event.dart": "c495cca0d838643a2bb6f0f1110d8fdc8cb838404feaa1c8866d5c62ba0be936", + "lib/presentation/app/screens/app.dart": "0fbd83a8eb8227809d02e8072f68325ee1578d8b39814453e41428e812c1a837", + "test/presentation/app/bloc/schedule/schedule_bloc_test.dart": "bc715a45d509e3203d7621efbbb6066b25f10ae1e198f4cab1d96d64f2d50a08", + "test/core/services/preparation_step_notification_test.dart": "5a9d45fe439c38ee00471e90fa849e7d9be65160e5f898aaa4bc1696679dcea7", + "test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart": "ad03928eeb7e1c4b55054b1cbf301d18104e8dd7add28504a062cf5ab6d532ae" +} diff --git a/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log b/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log index 76e903d4..eee6d628 100644 --- a/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log +++ b/plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log @@ -33,7 +33,7 @@ lib/presentation/startup/screens/local_reset_progress_screen.dart:9:9: Context: dart:async/schedule_microtask.dart 49:5 _startMicrotaskLoop dart:isolate-patch/isolate_patch.dart 127:13 _runPendingImmediateCallback dart:isolate-patch/isolate_patch.dart 193:5 _RawReceivePort._handleMessage - + 00:00 +0 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: (setUpAll) 00:00 +0 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout 00:00 +1 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database diff --git a/plans/audit-2026-09-23/d03-ci-validation.json b/plans/audit-2026-09-23/d03-ci-validation.json new file mode 100644 index 00000000..368dbcf5 --- /dev/null +++ b/plans/audit-2026-09-23/d03-ci-validation.json @@ -0,0 +1,42 @@ +{ + "head_commit": "074d5fd642a2890dc48933dafcbd038534f759a4", + "runs": [ + { + "conclusion": "success", + "databaseId": 35944442338, + "event": "pull_request", + "headSha": "074d5fd642a2890dc48933dafcbd038534f759a4", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35944442338", + "workflowName": "Flutter Testing", + "tests_passed": 882, + "coverage": { + "percent": 88.5, + "lines_hit": 13015, + "lines_found": 14706 + } + }, + { + "conclusion": "success", + "databaseId": 35944442335, + "event": "pull_request", + "headSha": "074d5fd642a2890dc48933dafcbd038534f759a4", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35944442335", + "workflowName": "Android Notification Contract", + "artifact_id": 10786008849, + "identity": { + "commit": "86396fb5cd3a747720a2365d02525a751702703c", + "artifact": "build/app/outputs/flutter-apk/app-release.apk", + "sha256": "826313ef6641e3b5a68d0ed614d64f68ca9da5360d1bb8cf0ec98af10ac589dd", + "signing": "disposable CI validation key; not a store release", + "merged_manifests": [ + "build/app/intermediates/merged_manifest/release/outputReleaseAppLinkSettings/AndroidManifest.xml", + "build/app/intermediates/merged_manifest/release/processReleaseMainManifest/AndroidManifest.xml", + "build/app/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml" + ], + "device_delivery_verified": false + } + } + ] +} diff --git a/plans/audit-2026-09-23/d03-main-integration-validation.json b/plans/audit-2026-09-23/d03-main-integration-validation.json index cdfca438..7e31f520 100644 --- a/plans/audit-2026-09-23/d03-main-integration-validation.json +++ b/plans/audit-2026-09-23/d03-main-integration-validation.json @@ -10,10 +10,14 @@ "artifacts": { "plans/audit-2026-09-23/artifacts/d03/integrated/recovery.png": "16e66a342fd6a330241b2cd0e191bda2c11082293fbea0a5b8e649fda1d31294", "plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-ui-capture-v2.log": "af5babf3ccb0d3d28014589f1a1573bd6d4babfdbb3d1fe5a7a13eddb97709f0", - "plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log": "c5311115364928fd5e275c709775f218183424cac4e097cf094572e34302d274", + "plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted.log": "144b0f59e5983c16fd9e5dc3d9e8db45760bfdafc8ea14a33fc40512ba14fbbc", "plans/audit-2026-09-23/artifacts/d03/integrated/d03-merge-targeted-v2.log": "5a4855e44988d76ca7cc60f2bce2adf5ea533e9a7d2d27315b129997ffc9fb14", "plans/audit-2026-09-23/artifacts/d03/integrated/mypage-entry.png": "74229da8a4c532f5da307d98b40776041970d13ad13201bb9791b8d0c3c9a5f5", "plans/audit-2026-09-23/artifacts/d03/integrated/recovery-confirm.png": "6dc0c6ea3fab24b5857f45353d10976590fcf9114d2bf82c10d22ef3a6cda073", "plans/audit-2026-09-23/artifacts/d03/integrated/recovery-busy.png": "cb4615d5f87fa6bdd14a61d83b05bd9aab822c1214c53d5aec0f7a2864c8319d" + }, + "normalized_initial_failure_log": { + "raw_sha256": "c5311115364928fd5e275c709775f218183424cac4e097cf094572e34302d274", + "change": "trailing whitespace only" } } diff --git a/plans/audit-2026-09-23/issues/A06.md b/plans/audit-2026-09-23/issues/A06.md index 3fd68f17..ebeadf75 100644 --- a/plans/audit-2026-09-23/issues/A06.md +++ b/plans/audit-2026-09-23/issues/A06.md @@ -156,3 +156,20 @@ ## 작성 시점 상태 read-only 코드/문서 조사와 실제 3문답을 마치고 이슈 본문만 작성했다. A06 소스 변경·테스트 실행·GitHub 게시·커밋은 아직 하지 않았다. root가 본문을 게시하고 우선순위·선행 의존성에 따라 구현을 진행한다. 확인된 결함과 예정된 검증을 완료 실적으로 혼동하지 않는다. + + +## 2026-09-24 구현과 검증 + +구현 커밋 `d06474d4c645794644075173f76afb9ff4445047`은 최신 main/Figma 변경 및 A15/D03 공통 owner 위에 적용했다. 실제 Timer.periodic의 Refresh 원인을 구별하고, wall clock+action events로 진행을 계산한다. 단조시간은 알림 신선도에만 사용한다. 연속 paused, 직전 관찰 간격2초 이하, wall/monotonic 차이250ms 이하, 같은 run의 인접 단계 전환만 한 번 소비한다. phase 왕복·resume·restore·수동 skip·지연·최초/마지막 단계는 늦게 재생하지 않는다. + +Legacy Tick.elapsed의 별도 누적을 제거해 같은 wall-derived refresh로 통합했다. 기존 테스트도 실제 clock 진행을 원천으로 수정했고 큰 Tick.elapsed만으로 진행/알림을 만들어서는 안 된다는 회귀를 남겼다. run/occurrence/data generation별 소비 기록은 분리된다. 표시 권한과 setup을 await한 뒤 show 직전 현재성을 검사하며 공통 alarm owner가 실제 show 반환과 reset cleanup을 직렬화한다. upcoming Schedule Notification Setting OFF는 활성 단계 알림을 막지 않는다. 일반 KO/EN 문구와 type/scheduleId/stepId 최소 payload를 유지한다. + +검증 결과: + +- 수정 전 실제 Timer.periodic→Refresh 회귀에서 다음 단계 UI는 반영되지만 알림 callback이0회여서 실패함을 확인했다. +- 신규 경계47개 통과. 실제 Timer→NotificationService→플러그인 MethodChannel show까지 연결한 경로, 정확2초/초과,250ms/초과, lifecycle왕복, skip뒤자동전환, sync/Future오류 뒤 다음단계,같은step ID의다른회차/run/generation,권한/setup대기중prompt/삭제/완료/reset 및owner순서 포함. +- 최종 전체 **929개**, coverage **88.74% (13092/14753)**, analyzer, generated/local-only, Python정책11개, Android source manifest 통과. +- 제품/테스트7파일을 source freeze 후 재해시했고 root가 커밋blob과도 대조했다. 실제회귀의 fakeAsync 안에서 Bloc.close 대기가 멈춘 하네스 문제는 종료만 tester.runAsync로 분리해 해결했다. 제품 타이머를 가짜 Tick 호출로 대체하지 않았다. +- 전체 근거는 `plans/audit-2026-09-23/a06-final-validation.json` 및 `artifacts/a06/`에 보존한다. 원본 로그 해시와 저장용 후행공백 정리를 구별했다. + +실제 Android/iOS background·잠금·resume·권한 표시 검증과 이 변경의 원격 CI는 별도 단계다. plugin show 완료는 제출 증거이며 OS에 표시됐다는 보장이 아니다. suspend/kill 동안 periodic 실행이나 모든 단계 정시 전달을 보장하지 않는다. 기기 수용조건이 남아 이슈는 OPEN을 유지한다. diff --git a/plans/audit-2026-09-23/issues/D03.md b/plans/audit-2026-09-23/issues/D03.md index 8e6bde71..2f6d1dfa 100644 --- a/plans/audit-2026-09-23/issues/D03.md +++ b/plans/audit-2026-09-23/issues/D03.md @@ -272,3 +272,5 @@ restore는 journal을 새 DB의 일부로 교체하지 않는다. 구세대 취 ### 최신 main 화면 통합 upstream `c7184b2a`의 Figma 화면 PR #607과 충돌해 D03 푸시 직후에는 PR CI가 생성되지 않았다. `659a5fc4`에서 디자인·실제 알림 상태·typed 초기화 결과를 함께 보존해 충돌을 해결했다. 초기 통합에서 복구 화면 인자명이 잘못 연결된 컴파일 실패를 발견했고 `074d5fd6`에서 `operation`으로 수정했다. 그 뒤 관련 화면/복구 **36개 테스트**와 실제 글꼴 캡처 **2개 테스트**를 통과했다. partial reset이 완료 callback을 부르지 않는 회귀도 추가했다. 원래 디자인 artifact는 유지하고 통합 화면 캡처를 `artifacts/d03/integrated/`에 별도로 보존했다. root가 마이페이지·복구·복구 대기 화면을 직접 확인했다. 로컬 실행 시 독립 A06 WIP가 존재했으므로 정확한 committed tree 검증은 원격 CI와 구별한다. + +최종 통합 원격 `074d5fd642a2890dc48933dafcbd038534f759a4`: Flutter run35944442338에서 **882개/analyze/coverage88.50%(13015/14706)**, Android run35944442335에서 **release APK·merged/packaged manifest** 검증을 통과했다. artifact10786008849, synthetic merge86396fb5, APK SHA256 `826313ef6641e3b5a68d0ed614d64f68ca9da5360d1bb8cf0ec98af10ac589dd`를 read-back했다. 일회용 CI 서명이며 store 출시·실제 기기 전달 증거가 아니다. `d03-ci-validation.json`에 원격 identity를 보존했다. diff --git a/plans/audit-2026-09-23/issues/U03.md b/plans/audit-2026-09-23/issues/U03.md new file mode 100644 index 00000000..a757535d --- /dev/null +++ b/plans/audit-2026-09-23/issues/U03.md @@ -0,0 +1,194 @@ +# [P1][U03] 상세 알림 토글의 저장·반영 상태와 빠른 조작을 일관되게 처리 + +## 문제와 사용자 영향 + +‘알림에 일정 이름 표시’는 잠금화면 개인정보 노출 범위를 바꾸는 설정이다. 현재 화면은 요청한 값을 먼저 체크하고 DB 저장을 기다린다. 저장 예외를 처리하지 않으며, 저장 중 반대 조작을 받은 경우 어떤 선택이 최종 적용되는지 보장하지 않는다. 저장 후 알림 재등록이 실패하면 일시적인 공통 SnackBar만 보여 준다. 사용자는 화면의 OFF를 보고 기존 알림도 비공개로 바뀌었다고 생각할 수 있지만, DB 저장 여부와 기존 상세 알림 취소 여부는 별개의 사실이다. + +2026-09-23 종합 감사 **U03 / P1**의 상세 명세다. 전담 서브에이전트 `/root/grill_u03`가 grill-with-docs로 실제 질문하고 root가 사용자를 대신해 답했다. 이 문서는 합의한 후속 구현 계획이며, U03 구현·테스트·실기기 완료를 주장하지 않는다. A05/A14/A15/D03의 이미 구현된 공통 알림 엔진을 활용하되, 각 이슈의 미완료 기기 조건까지 완료된 것으로 간주하지 않는다. + +## 고정 소스 근거 + +조사 기준은 main/Figma PR #607 통합 커밋 **`074d5fd642a2890dc48933dafcbd038534f759a4`**다. A06의 working tree 변경과 U01의 문서 변경은 이 조사 근거에 섞지 않았다. 모든 제품 코드 근거는 `git show`로 고정 커밋에서 읽었다. 실제 구현 시작 시 최신 통합 소스를 다시 확인한다. + +| 근거 | 현재 확인한 동작 | +|---|---| +| [상세 설정 초기 읽기 L264–290](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/presentation/my_page/my_page_screen.dart#L264) | `_enabled=false`, `_loading=true`에서 시작한다. `_load`가 예외를 처리하지 않아 실패 시 로딩과 미처리 Future가 남을 수 있다. 미확인 상태도 화면 값은 false다. | +| [상세 설정 변경 L293–325](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/presentation/my_page/my_page_screen.dart#L293) | UI 선반영→catch 밖 DB write→reconcile 순서다. 저장 중 재입력·요청 순번·read-back이 없고 재등록 partial/settingsUnavailable만 공통 SnackBar로 표시한다. 제목은 한국어 상수다. | +| [DetailedNotificationPreferenceService L11–20](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/core/services/detailed_notification_preference_service.dart#L11) | DB의 상세 설정 조회·갱신만 수행한다. UI 의도, 실패 read-back, generation, 알림 결과를 소유하지 않는다. | +| [UserDao L141–164](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/data/daos/user_dao.dart#L141) | 설정 read는 profile row 부재 시 기본값을 반환한다. 상세 write는 transaction으로 필드만 갱신하며 실제 변경에만 durable revision을 증가시킨다. | +| [Reconcile 접수·drain L74–130](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/domain/use-cases/reconcile_alarms_use_case.dart#L74) | A15 요청 revision과 lease가 이미 있다. 각 요청은 해당 cutoff의 pass 결과를 받으며 단순 in-flight 합류 구조가 아니다. U03은 이 경계를 이용해야 한다. | +| [Reconcile read-back L480–539](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/domain/use-cases/reconcile_alarms_use_case.dart#L480) | provider 관측과 실패를 반영해 confirmed/retained record를 구분한다. UI가 enum 하나만으로 ‘잠금화면 완전 비공개’를 선언할 근거는 아니다. | +| [AlarmReconciliationResult L408–450](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/domain/entities/alarm_entities.dart#L408) | status, failures, provider, armed ID를 제공하지만 UI의 DB 저장 상태 및 특정 상세 의도와 연결된 적용 상태는 별도로 모델링해야 한다. 필요한 경우 최소 typed 이유를 확장한다. | +| [AlarmOperationCoordinator](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/core/services/alarm_operation_coordinator.dart) | OS 부작용을 공통 owner가 직렬화하고 D03 journal 최소 소유권을 보존한다. UI timeout으로 owner를 해제할 수 없다. | +| [LocalDataOperationGate](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/core/database/local_data_operation_gate.dart) | replacement/invalidation 시 generation이 바뀐다. UI dispose와 데이터 세대 변경은 다른 경계다. | +| [App resume L151–174](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/presentation/app/screens/app.dart#L151) | resume의 공통 reconcile 요청은 존재한다. 상세 설정 tile 자체는 lifecycle 관찰/최신 상태 구독을 하지 않는다. | +| [기존 MyPage 테스트 L104–142](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/test/presentation/my_page/my_page_screen_test.dart#L104) | opt-in 저장과 commit 후 reconcile exception을 검사한다. write/read-back 실패·연속 요청·화면 이탈·replacement 경계 검증은 부족하다. | +| [Backup Restore L175–201](https://github.com/DevKor-github/OnTime-front/blob/074d5fd642a2890dc48933dafcbd038534f759a4/lib/core/backup/backup_service.dart#L175) | 복원 데이터에 상세 선호가 포함되고 generation replacement 안에서 DB를 교체한다. 구화면의 늦은 상세 write가 복원 설정을 덮으면 안 된다. | + +## 도메인과 범위 + +- **Private Notification Content**가 기본이며 **Detailed Notification Content**는 명시적 opt-in이다. 상세 ON이 허용하는 내용은 일정명과 기기 시간대와 다른 Schedule Time Zone뿐이다. Place, note, Preparation 및 Preparation Step 원문은 제외한다(ADR 0030). +- 상세 선호는 암호화 **Local Data Store**에 있는 **Durable OnTime Data**다. 현재 OS 예약·취소 상태와 같지 않으며, **OnTime Backup**에는 선호만 포함한다. +- **Schedule Notification Setting**은 미래 일정 준비 알림 사용 여부다. 상세 내용 설정과 OS 표시 권한은 서로 다른 축이다. 진행 중 Preparation Step 알림의 A06 설정 의미를 변경하지 않는다. +- **Local Data Reset**, **Backup Restore**, **Backup Cutoff** 및 기존 A15 generation 계약을 사용한다. 새 제품 용어는 없다. requested/confirmed/applying/controller/revision 같은 구현 표현을 CONTEXT glossary에 추가하지 않는다. 새 ADR도 필요 없다. +- Android/iOS local-only, schema 2, backup v2/v1 호환, nearest eligible 60개, Android 신규 native alarm 금지 및 현재 Figma/Flutter MyPage 구성을 유지한다. 서버·원격 분석·별도 durable 설정 큐·원문 알림 payload 저장소는 추가하지 않는다. + +## 확정한 사용자 상태와 저장 계약 + +### 읽기, 최신 요청, 확정값 + +1. 최초 active Local Profile 및 데이터 gate readiness가 확인되기 전에는 실제 저장값을 안다고 표시하지 않는다. 설정 읽기 중에는 ‘확인 중’, 오류에는 ‘설정을 불러오지 못함’과 명시적 재시도를 제공한다. false로 가정해 저장하거나 profile을 새로 만들지 않는다. 새 설치의 정상 private 기본값과 읽을 수 없는 상태를 구분한다. +2. 확인된 DB 값, 사용자가 마지막으로 요청한 값, DB 저장 상태, OS 반영 상태를 구분한다. UI가 요청값을 스위치에 즉시 표시할 수 있지만 저장 전에는 ‘끔 요청 중/켬 요청 중’처럼 미확정임을 보조 문구와 접근성 상태로 명시한다. +3. 최신 의도 하나를 직렬로 처리한다. ON 저장/알림 반영이 진행 중이어도 OFF 요청을 접수한다. 빠른 ON→OFF→ON은 최신 값에 수렴한다. 저장과 OS writer를 병렬 복제하지 않는다. +4. 오래된 write가 이미 commit될 수는 있지만 최신 OFF가 대기 중이면 그 오래된 ON 결과를 이유로 새 detailed OS 등록을 시작하지 않는다. currentness 검사와 A15 후속 요청 접수를 연결한다. 이미 OS에 전달된 호출은 실제 반환·취소 확인을 D03/A15가 소유한다. +5. write 실패 뒤 authoritative DB read-back으로 마지막 확정값을 알아낸다. 실패를 이유로 추정 rollback write를 하지 않는다. read-back도 실패하면 unknown이고 ON/OFF 저장 성공을 표시하지 않는다. 오류 자체가 무한 재시도를 만들지 않으며 명시적 재시도/새 조작으로 회복한다. +6. DB에 commit된 ON/OFF는 OS 실패 때문에 되돌리지 않는다. 특히 OFF를 상세 ON으로 rollback하거나 상세 내용으로 재등록하는 가용성 fallback은 금지한다. +7. 한 write의 늦은 결과, 초기 읽기의 늦은 결과, 과거 reconcile 결과는 최신 요청·현재 generation의 UI를 덮지 않는다. 최신 의도가 같은 DB 값이면 불필요 write 및 dataRevision 증가는 없다. + +### DB 저장과 알림 적용을 구분하는 상시 표시 + +| 상태 | 표시 의미와 행동 | +|---|---| +| 최초 읽기 지연/실패 | 미확인/읽기 오류를 표시하고 미확인 스위치 조작을 막는다. 읽기 재시도를 제공한다. | +| 최신 값 DB 저장 중 | 요청값과 ‘저장 중’을 표시한다. 반대 의도는 접수 가능하며 완료라고 하지 않는다. | +| write 실패, read-back 성공 | 실제 확정값과 아직 저장되지 않은 요청/오류를 구분한다. 명시적 재시도 또는 새 선택을 받는다. | +| write 실패, read-back 실패 | 현재 값 확인 필요. 가짜 OFF/ON 성공 및 추정값 기반 알림 조정을 하지 않는다. | +| OFF 저장 성공, 상세 취소 미확인 | ‘끔으로 저장됨 · 기존 알림에 일정 이름이 남아 있을 수 있음’처럼 privacy 적용 미완료를 지속 표시한다. | +| OFF 저장 성공, 기존 취소 확인, private 예약 실패 | 저장값은 OFF로 유지하고 ‘일부 알림 예약 실패’ 또는 전달 공백을 알린다. 이전 상세 등록을 복원하지 않는다. | +| ON 저장 성공, 예약 일부 실패 | ON 저장 사실과 일부 적용 실패를 구분한다. 성공한 대상만 근거에 맞게 집계한다. | +| 관측 불가/영속 자료만으로 세부 privacy 결과 구분 불가 | 보수적인 ‘알림 적용 확인 필요’를 표시한다. 과거 원문을 보관해 상태를 추론하지 않는다. | +| 전체 Schedule Notification Setting OFF | 상세 선호는 저장 가능하되 ‘일정 알림 꺼짐’과 구분한다. 남은 취소 미확인이 있으면 감추지 않는다. | +| 권한 부족 | 설정 저장 실패로 취급하지 않는다. 권한 상태 및 사용자가 선택하는 기존 설정 이동 경로를 제공한다. 자동 권한 팝업을 반복하지 않는다. | +| 현재 eligible 미래 일정 없음 | ‘예약된 알림 없음’. 저장 성공은 표시할 수 있으나 존재하지 않는 예약을 적용 성공 건수로 만들지 않는다. | +| 알림 작업 Future 미반환 | 제한된 대기 뒤 ‘확인 지연’을 안내한다. timeout은 성공/취소 완료/owner 해제 의미가 아니며 중복 retry를 접수하지 않는다. | + +SnackBar는 보조 안내일 뿐 유일한 상태 저장소가 아니다. 재진입/resume에서 DB와 현재 coordinator의 조정·관측 상태를 다시 확인하여 미완료를 복구 표시한다. 실제 platform 관측이 지원하지 않는 사실은 ‘확인됨’으로 바꾸지 않는다. Android plugin cache와 실제 OS read-back의 차이는 A14 기준을 유지한다. pending 존재/등록 요청 성공이 미래 잠금화면 전달 보장은 아니다. + +‘알림 다시 적용’은 현재 확정 선호를 읽어 delivery 단계만 재시도한다. 동일 선호를 DB에 다시 쓰거나 dataRevision을 증가시키지 않는다. read 미확정이면 임의 bool을 reconcile에 전달하지 않는다. 호출이 미반환 상태면 같은 진행 상태를 보여 주고 별도 provider 작업을 만들지 않는다. 새 generation 이후 구요청 재시도는 금지한다. + +## 화면·생명주기·데이터 경계 + +1. 화면은 저장/알림 적용 중에도 이탈할 수 있다. 접수된 최신 의도는 화면보다 오래 사는 현재 설치 generation 범위의 작은 상세 설정 controller/service가 계속 소유한다. 화면 dispose가 대기 중 OFF를 버려 앞선 ON만 남기는 동작은 허용하지 않는다. +2. 재진입은 동일 진행 상태에 구독한다. 이탈 뒤 setState·SnackBar를 호출하지 않고 모든 Future 실패를 controller의 원문 없는 안전한 오류 유형으로 보관해 다음 구독에서 보여 준다. 다른 설정·일정 전체를 처리하는 전역 범용 큐를 새로 만들지 않는다. +3. process kill 전에 DB에 저장되지 않은 메모리 의도의 보존은 약속하지 않는다. 재시작은 DB committed 선호와 기존 D03 journal/관측에서 현재 상태를 재구성한다. 새 durable preference intent journal은 만들지 않는다. +4. restore/reset 시작으로 설치 generation이 바뀌면 아직 시작하지 않은 의도를 폐기한다. 이미 시작한 write가 새 DB에 침투하지 못하게 실제 gate/transaction 경계를 연계한다. await 뒤 UI 결과만 무시하는 것으로 충분하다고 간주하지 않는다. 이미 commit된 구세대 설정을 복원된 DB에 다시 쓰지 않는다. +5. backup은 commit과 snapshot의 순서에 따른 기존 Backup Cutoff 계약을 유지한다. 컷오프 이전 committed 선호는 포함되고 이후 변경은 이후 active data로 남는다. 파일 선택 화면이 열린 시간만으로 설정 의도를 이전 snapshot에 소급 포함하지 않는다. +6. 초기 Q1 응답의 ‘현재 screen/controller lifetime’은 화면 이탈 시 OFF를 버리라는 의미가 아니다. Q3에서 명시적으로 현재 설치 generation에 묶인 화면보다 긴 controller 소유로 정정했다. + +한국어/영어에 이 row의 제목, private/detailed 허용 범위, 로딩·저장·취소 미확인·예약 실패·확인 지연·재시도 의미를 제공한다. 200% 글자 크기, 작은 휴대전화 너비, 스크린리더에서 checked/requested/busy/error 의미를 잃지 않게 한다. 단순 색상이나 애니메이션만으로 상태를 구별하지 않고 긴 경고문과 retry가 잘리거나 다른 설정을 가리지 않게 한다. 일정명·준비명 sentinel을 오류 메시지/로그/스크린샷 증거에 불필요하게 재사용하지 않는다. 앱 전체 localization은 U05다. + +## 구현 경로와 의존성 + +| 범위 | 책임 | +|---|---| +| `lib/presentation/my_page/my_page_screen.dart` 및 필요한 작은 상세 설정 controller/state | 요청·확정·진행·오류·이탈/재진입 상태, KO/EN/접근성 UI. 현재 MyPage의 다른 설정과 Figma 구조를 보존한다. | +| `lib/core/services/detailed_notification_preference_service.dart` 또는 domain use case로 정리한 작은 설정 경계 | DB read/write, 현재 generation 유효성, 최신 의도 직렬 실행. UI가 직접 여러 부작용을 조합하지 않게 하되 범용 전역 작업 프레임워크를 만들지 않는다. | +| `lib/domain/use-cases/reconcile_alarms_use_case.dart`, `alarm_entities.dart` | 필요한 경우 최소 typed 반영 이유/요청 결과 연결을 확장한다. content 재작성·provider 정리 엔진을 복제하지 않는다. | +| `LocalDataOperationGate`, DB transaction/restore/reset 호출 경계 | 늦은 구세대 write 방지. UI의 generation 체크만으로 await 이후 실제 DB write를 보호했다고 주장하지 않는다. | +| `lib/l10n/app_ko.arb`, `app_en.arb` 및 기존 tracked l10n 산출물 | U03 문구를 일관되게 현지화한다. 기타 ignored generator 출력은 commit하지 않는다. | +| `test/presentation/my_page/my_page_screen_test.dart`, 새 필요 controller/service 테스트, 실제 DAO·owner 통합 테스트 | 화면만 정상으로 보이는 fake 테스트에 그치지 않고 DB/최종 provider/실패 ownership을 함께 확인한다. | + +- **A04 #587**: 무관한 profile 저장이 선호를 덮지 않는 선행 계약. +- **A05 #588**: content 비교·기존 상세 취소·private 교체. U03은 이를 재구현하지 않고 실제 결과를 표시한다. +- **A14 #593**: 플랫폼별 present/absent/unknown 및 실제 provider 집계. UNKNOWN은 private 성공이 아니다. +- **A15 #594**: 각 요청 cutoff, 최신 committed 값 재조정, 단일 owner, replacement generation. U03의 최신 UI 의도는 DB에 commit된 후 이 진입점으로 접수한다. +- **D03 #595**: 최소 영속 ownership, 취소 미확인·재시작 복구. UI를 위해 원문이나 새 durable pending preference를 journal에 넣지 않는다. +- **A06 #598**: 진행 중 Preparation Step 알림과 미래 Schedule Notification 설정 의미 유지. 상세 변경에 blanket cancelAll을 쓰지 않는다. +- **A08/C01, A09 #602, D01 #603**: DB snapshot/replacement/readiness 관련 전체 계약. U03에서 필요한 실제 write 경계는 통합 검증하되 이 이슈 하나로 전체 백업·복원·startup를 완료 처리하지 않는다. +- **U02, U05**: 상세 content의 differing zone 허용 범위와 전체 localization 계획. U03은 기존 상세 content 의미를 확장하지 않는다. + +## 수용 기준과 검증 시나리오 + +Completer/barrier를 사용해 실제 await 경계에 지연·실패·후속 의도를 삽입한다. 임의 sleep이나 구현 필드 값만 검사하지 않는다. 합성 일정과 임시 DB/파일, 명시적 fake OS 상태로 UI·durable preference·provider 내용·최소 취소 증거를 함께 대조한다. + +| 번호 | 시나리오 | 기대 결과 | +|---|---|---| +| 1 | 최초 read 지연 | 미확인 표시, 쓰기 0, 조작 비활성, false 저장 없음. | +| 2 | 최초 read 오류→명시 retry 성공 | 예외 미누출, 읽기 오류/재시도 후 실제 저장값 표시. | +| 3 | 초기 read 결과가 새 요청 뒤 늦게 반환 | 오래된 snapshot이 최신 UI/요청값을 덮지 않음. | +| 4 | OFF→ON 정상 | 허용 내용 설명, DB commit, 해당 요청의 현재 reconcile 결과를 구분해서 표시. | +| 5 | ON→OFF 정상 | OFF commit 후 기존 상세 철회/비공개 재예약, 최신 private 결과 표시. | +| 6 | ON write 지연 중 OFF 접수 | OFF 의도 유지, 과거 ON 결과로 신규 상세 예약 시작 안 함, 최종 DB/OS private. | +| 7 | ON→OFF→ON burst | 최신 의도에 수렴하며 write/provider 병렬 중복 없음. | +| 8 | OFF/ON 동일값 중복 입력 | 불필요 DB revision 증가/중복 알림 작업 없음. | +| 9 | write 실패, read-back 이전값 | 미저장 오류와 실제 확정값 표시, 추정 rollback write 없음. | +| 10 | write 응답 실패지만 read-back 요청값 | commit 사실과 OS 미확정 단계 구분; 같은 write 중복/리비전 증가 방지. | +| 11 | write와 read-back 모두 실패 | unknown 유지, 성공 문구/임의값 재등록 없음. | +| 12 | OFF commit 후 기존 상세 취소 실패 | OFF 유지, 이름 잔존 가능성 안내, tombstone 유지, latest private armed 거짓 성공 없음. | +| 13 | 취소 확인 후 private 예약 실패 | OFF 유지, 전달 공백 안내, old detailed 복원 없음. | +| 14 | 여러 대상 중 부분 실패 | 성공 대상과 미확인 대상 혼동 없음, 전체 성공 안내 없음. | +| 15 | retry 후 회복 | 현재 DB값으로 delivery만 실행, 선호 write/dataRevision 증가 없음. | +| 16 | OS 관측 unavailable/unknown | 확인 필요 표시, empty/success로 처리하지 않음. | +| 17 | 권한 부족/전체 알림 OFF/미래 일정 없음 | 각각 저장 실패와 구별, 이전 cleanup 미확인 감추지 않음. | +| 18 | provider Future 미반환→UI 제한 대기 | 지연 안내, owner 유지, 중복 retry 불가, 늦은 반환은 원래 owner가 회수. | +| 19 | OFF 대기 중 화면 이탈→재진입 | 접수 의도 유실 없음, 동일 진행 상태 구독, disposed setState/SnackBar/Future 예외 없음. | +| 20 | resume와 사용자 입력 경합 | 현재값과 최신 결과만 반영, 오래된 resume read가 최신 선택을 덮지 않음. | +| 21 | restore/reset이 write 전/중/직후 진입 | 구세대 queued 의도 폐기, 구 write가 신세대 DB를 덮지 않음, 늦은 OS 결과도 기존 owner가 정리. | +| 22 | 과정 중 프로세스 종료→재시작 | 미저장 intent durable 주장 없음, DB committed 값과 D03 journal만 사용해 현재 조정. | +| 23 | backup snapshot 전/후 preference commit | Cutoff에 따라 일관된 값 포함, 새 plaintext intent/상세 payload 백업 없음. | +| 24 | schedule 변경/locale·zone 변경과 동시에 조작 | A05 canonical 내용과 A15 최신 cutoff로 수렴; 허용되지 않은 민감 필드 없음. | +| 25 | unrelated 알림 및 active Preparation Step 존재 | 상세 Schedule Notification 처리만 수행, blanket cancelAll 없음. | +| 26 | KO/EN·작은 화면·200%글자·스크린리더 | loading/requested/saved/applying/failed 의미와 retry 조작 접근 가능, 문구 잘림/overflow 없음. | +| 27 | privacy 원문 sentinel | 새 상태·로그·journal·오류에 일정/장소/준비 원문 불필요 복제 없음. | +| 28 | Android/iOS 실제 설정 변경 | 기기별 허용 관측과 실제 새 잠금화면 표시를 구분해 기록, 미실행 provider는 미검증으로 남김. | + +실행 순서는 필요한 codegen, 변경 범위 focused tests, analyze, full Flutter tests 및 기존 CI 정책 검사다. 테스트 결과에는 실제 SHA와 실행 로그/CI URL을 기록한다. GitHub issue 문서 생성만으로 위 시나리오를 통과했다고 체크하지 않는다. 실기기에서는 합성 데이터로 상세 ON 예약→OFF→실제 미래 전달, 취소 미확인 안내, 재진입/권한 복귀를 확인한다. 기존 사용자 앱/시뮬레이터 데이터는 지우지 않는다. Android cache 관측, iOS pending/AlarmKit 관측, 실제 화면 전달은 각각 다른 증거로 적는다. + +## 완료 체크리스트 + +- [ ] 최초 읽기·write·read-back 실패에서 미처리 Future와 거짓 확정값이 없다. +- [ ] 접수된 최신 의도가 빠른 조작·화면 이탈 중 보존되고 구결과가 최신 상태를 덮지 않는다. +- [ ] ON/OFF DB 선호와 OS 적용 단계·privacy 취소 실패·예약 공백을 구분한다. +- [ ] retry/resume/재진입이 현재 확정값으로 안전하게 수렴하며 불필요 DB revision·중복 owner가 없다. +- [ ] restore/reset generation과 실제 write 경계를 통합 검증해 구세대 쓰기가 새 데이터에 침투하지 않는다. +- [ ] privacy 기본값/허용 필드/최소 journal/A06 설정 의미와 기존 Figma UI를 유지한다. +- [ ] 한국어/영어/200%접근성 검증 및 focused/analyze/full/CI 결과를 실제 SHA에 연결한다. +- [ ] 지원 mobile provider별 실기기 증거와 미확인 제한을 남긴다. 미검증 기기 조건이 있으면 OPEN을 유지한다. + +## 실제 grill-with-docs 문답 전체 + +스킬: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. 전담 agent는 먼저 스킬·고정 소스·CONTEXT·ADR 0030 및 A05/A14/A15/D03 문서를 읽었다. 코드로 답할 수 있는 현재 동작은 조사했고, 아래 정책 질문은 한 번에 하나씩 전송한 뒤 응답을 기다렸다. root 응답은 사용자가 위임한 의사결정이며 별도 사용자 인터뷰를 했다고 표현하지 않는다. + +### 적용 및 조사 안내 + +**Agent → root:** +> U03에서 grill-with-docs 스킬을 적용합니다. 고정 HEAD 074d5fd6의 상세 알림 설정·재등록 상태와 기존 도메인 계약부터 확인한 후, 코드로 결정할 수 없는 질문만 한 개씩 보내겠습니다. + +### 질문 1 — 최신 의도와 저장 실패 + +**Agent → root:** +> U03 질문 1/진행 방식. 고정 HEAD 074d5fd6의 상세 토글은 UI를 먼저 바꾸고 DB 저장을 await하며, 저장 중 재입력 차단/최신 의도 보존이 없습니다. A05는 DB에 저장된 OFF를 OS 실패 때문에 ON으로 되돌리지 말라고 정했지만, ON 저장·예약 중 사용자가 즉시 OFF를 누르는 정책은 아직 없습니다. 권장: 최초 읽기 성공 전에는 값 미확인+비활성·재시도, 읽은 뒤 조작은 최신 의도 1개를 유지하는 직렬 처리로 받습니다. 저장 중에도 OFF 요청을 접수해 바로 '끔 요청 중'으로 표시하되 확정 OFF/적용 완료라고 하지 않고, 앞선 write가 끝나면 최신 값만 commit합니다. 여러 write/OS 작업은 병렬로 만들지 않고 ON→OFF→ON burst는 최종 의도로 수렴합니다. 저장 실패 시 DB를 다시 읽어 확정값과 사용자가 요청한 값/미저장 오류를 구분하고 무한 재시도는 하지 않습니다. 단순 저장 중 스위치 잠금은 즉시 privacy OFF를 누를 수 없으므로 권장하지 않습니다. 이 최신 의도 접수 정책으로 확정할까요? + +**Agent → root, 추가 조사:** +> U03 추가 조사 사실: 현재 상세 토글의 _load 저장소 오류는 catch되지 않고 로딩이 유지될 수 있고, _change의 DB write도 catch 밖입니다. 재등록 결과는 partial/settingsUnavailable만 SnackBar로 보여 주며 화면 재진입·resume 때 상세 설정/적용 상태를 갱신하지 않습니다. 기존 테스트는 happy path와 DB commit 후 reconcile exception만 있어 DB 지연/실패·빠른 입력·disposed widget·generation 변경을 검증하지 않습니다. A14/A15/D03 계약을 UI에 연결하는 범위로 문서를 작성 중이며, 해당 하위 엔진을 완료 전으로 오인해 재설계하지 않겠습니다. + +**Root → agent (원문):** +> Q1권고채택. 초기읽기unknown은임의false표시/저장하지않고disabled+retry. 확인값/요청값/DB저장상태/OS적용상태구별, 최신의도1개직렬수렴. OFF요청은저장중에도받아끔요청중,아직비공개적용완료라하지않음. 이전ONwrite가이미DB반영될수있지만최신OFF대기중이면구형ON결과로새detailedOS등록을시작하지않도록currentness/A15후속요청경계에연결. 이미OS수락한ON은D03실제취소확인필요. write실패후authoritativeDBreadback으로확정/미저장구별,readback도실패면unknown이며ON/OFF성공거짓표시금지. 최신의도보관은현재screen/controllerlifetime내 ephemeral,restore/resetgeneration바뀌면폐기. 무한재시도없음,명시retry/새조작만재시도. 취소되지않는구형Future결과가최신UI/DB의도를덮지않아야함. + +Q1의 lifetime 표현은 아래 Q3 답변에서 화면보다 긴 설치 generation controller로 정정되었다. 위 원문은 실제 의사결정 과정을 보존하기 위해 그대로 남기고 최종 계약은 Q3를 적용한다. + +### 질문 2 — 반영 결과와 재시도 + +**Agent → root:** +> U03 질문 2/DB 저장 후 알림 반영 실패와 재진입. 권장: 토글 체크값은 DB에 저장된 선호 또는 명확히 표시된 최신 요청값이고, 아래 상시 보조 상태를 별도로 둡니다. OFF 저장+기존 상세 취소 실패는 '끔으로 저장됨 · 기존 알림에 일정 이름이 남아 있을 수 있음'과 '알림 다시 적용'; 취소 성공+private 등록 실패는 '끔으로 저장됨 · 일부 알림 예약 실패'로 구분합니다. ON 저장+예약 실패도 저장값 유지, 권한 부족/알림 전체 OFF/미래 일정 없음은 각각 상태이지 저장 실패가 아닙니다. resume/재진입 때 DB와 최신 조정 결과를 읽어 이전 SnackBar가 사라져도 미해결 상태를 다시 표시하고, retry는 저장 재실행/DB revision 증가 없이 현재 선호로 알림 조정만 수행합니다. 조정이 반환하지 않으면 제한된 시간 뒤 '확인 지연'을 표시하되 Future/owner를 강제로 해제하거나 중복 retry를 만들지 않습니다. 기존 result만으로 privacy 철회 실패와 예약 공백을 구별할 수 없으면 U03에 필요한 최소 typed 결과를 확장합니다. 단순 '모두 적용됨/이름이 완전히 삭제됨' 보장은 하지 않습니다. 이 상태·재시도 계약을 확정할까요? + +**Agent → root, 진행 근거:** +> U03 문서 근거는 074d5fd642a2890dc48933dafcbd038534f759a4에 고정했습니다. A06 변경 중 파일은 git show로만 확인했고, 제품·테스트·공유 문서는 수정하지 않았습니다. 질문 2 답변 뒤 화면 이탈 시 접수된 OFF 의도/restore·reset 경계를 마지막 질문으로 확인하겠습니다. + +**Root → agent (원문):** +> Q2채택. OFF저장후취소미확인은기존일정이름이남을수있음,취소확인후private등록실패는예약공백으로구분. unknown/관측불가를비공개완료라하지말고보수적확인필요. 현재공통result불충분시최소typedreason확장가능하되새private원문/영속detailedpayload추가금지,과거내용보존으로상태추론하지않음. ON/OFF저장값OS실패때rollback안함. 전체알림OFF/permission/noUpcoming은별도표시,active step알림은A06설정의미유지. 재진입/resumeDB+최신coordinator관측갱신,이전snackbar에의존않음. retry는기존현재값read후조정만,동일값DB쓰기/revision증가없음. 미반환Future동안확인지연안내+retry중복불가,시간초과소유권해제금지. save미확정readback실패는재조정에임의값을넘기지않음. 영속된자료만으로privacy철회상태를구체확정못하면일반확인필요로안전하게설명. + +### 질문 3 — 화면 이탈과 데이터 세대 + +**Agent → root:** +> U03 질문 3/화면 이탈과 데이터 세대. ON write 대기 중 OFF가 접수됐는데 사용자가 뒤로 나가면 화면 dispose가 OFF 의도를 버려 ON만 저장되는 위험이 있습니다. 권장: 화면은 즉시 이탈 가능하되, 접수된 최신 의도는 화면보다 긴 설치 세대 범위의 작은 controller/service가 DB write와 후속 조정까지 계속 소유합니다. controller는 전역 범용 작업 큐가 아니며 한 상세 설정 흐름만 담당하고, 재진입은 같은 진행 상태에 구독합니다. 이탈 후 setState/SnackBar는 없고 catch되지 않은 Future도 없어야 합니다. process kill 전 미저장 의도는 durable하다고 약속하지 않으며 재시작은 DB만 읽습니다. restore/reset 시작의 generation 변경은 아직 시작 안 한 의도를 폐기하고 이전 세대 write가 새 DB에 침투하지 않게 gate/transaction 경계를 조정합니다; 이미 commit된 값은 복원 DB를 덮는 재적용 대상이 아닙니다. 실제 snapshot 진행 중 backup은 commit 순서의 Cutoff대로 포함되며 새 intent journal을 만들지 않습니다. 이 방식으로 확정할까요? 저장 완료까지 화면을 잠그는 대안은 이탈 마찰과 무응답 차단 때문에 비권장입니다. + +**Agent → root, 추가 경계:** +> U03 추가 경계: 현재 getAlarmSettings는 profile row가 없으면 기본 false를 반환합니다(UserDao L141–149). 새 설치의 확정된 private 기본값과 restore/reset/초기화 중 아직 읽을 수 없는 프로필을 구분해야 하므로, U03 controller는 active Local Profile/gate readiness가 확인된 경우에만 이 기본값을 읽어 보여 주는 계약을 적겠습니다. 임의 empty profile 생성이나 DAO 기본값 전면 변경은 D01/C01 범위를 침범하지 않습니다. 새 도메인 용어/ADR은 필요 없고, confirmed/requested/applying 같은 UI 상태는 구현 개념으로만 둡니다. + +**Root → agent (원문):** +> Q3채택. Q1의'현재screen/controllerlifetime내'는화면dispose때접수된OFF까지버리는뜻이아님을정정: controller는현재설치generation내에서화면보다길게소유,재진입동일진행구독,processkill미저장보장없음. 설치generation변경시queuedintent폐기, in-flight write가새DB에침투못하도록기존gate/transaction연계(단순afterawait UI검사만으론부족), commit된구세대설정복원DB에재적용금지. backupcutoff동작도권고채택,새영속intentjournal추가안함. 화면이탈가능,실패는controller안전상태로보관하고다음구독에표시; 원문없는오류유형만. activeProfile/gate readiness확인없이DAOdefaultfalse를성공읽기로쓰지않는추가경계채택. 초기설치온보딩엔기존private기본정책유지,빈profile임의생성없음. 새용어/ADR없음. 실제3문답과이정정/추가근거모두포함U03.md작성동결해주세요. + +## 문서 작성 시 실행 경계 + +이 세션에서 수행한 것은 스킬·고정 소스 읽기, 실제 3문답, 이 이슈 명세 작성뿐이다. 제품 코드, 테스트, QA 도구, shared CONTEXT/ADR, state/README를 변경하지 않았다. Flutter 실행, git mutation, GitHub 게시를 수행하지 않았다. 문서 게시와 실제 구현·검증은 root가 우선순위 흐름에서 별도로 수행한다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 23a0d75a..f3b2b78a 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -358,7 +358,7 @@ "`local_data_reset_service.dart:19-25`; `local_data_lifecycle.dart:45-69`; `cancel_schedule_alarm_use_case.dart:25-37`; `cancel_all_alarms_use_case.dart:19-41`; `reconcile_alarms_use_case.dart:549-570`; 관련 테스트 `:951-975`", "이미 없음과 실제 취소 실패 구분, 실패 기록 보존·재시도·OS read-back. 중간 강제 종료 후 정리 완료. reset marker는 검증 가능한 종료 상태까지 유지." ], - "status": "code_verified_ci_device_pending", + "status": "code_verified_device_pending", "agent": "/root/grill_d03", "issue_number": 595, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/595", @@ -368,18 +368,20 @@ "verification": [ "Final full872 and focused71 passed; coverage87.99% (12253/13926); analyze/generated/local-only passed", "Product/test freeze unchanged; QA-only explicit unknown fixture correction then all11 host SIGKILL cases passed", - "SDK Swift typecheck/Kotlin compile; Python11/manifest passed; KO/EN 200% four widget PNGs visually reviewed" + "SDK Swift typecheck/Kotlin compile; Python11/manifest passed; KO/EN 200% four widget PNGs visually reviewed", + "Remote074d5fd6 Flutter882/analyze/88.50% and Android APK/manifest success; artifact10786008849 identity read back" ], "blockers": [ - "Actual Android/iOS delivery, process interruption recovery and backup-exclusion device read-back pending", - "Remote CI and merge pending" + "Actual Android/iOS delivery, interruption recovery and backup-exclusion device read-back pending", + "Merge and release pending" ], "sequence": 11, - "readback_verified": false, - "body_sha256": "0abd28107d757b22229532d9a9d6659b549252f8ec26b70da2ee4586101ae92f", + "readback_verified": true, + "body_sha256": "8c7ae3ed41f7d9f9c9d8622a7c62e2c5ae21486ef212b63d0ba34a5604647fbc", "progress_file": "plans/audit-2026-09-23/d03-journal-progress.json", "validation_file": "plans/audit-2026-09-23/d03-final-validation.json", - "integration_validation_file": "plans/audit-2026-09-23/d03-main-integration-validation.json" + "integration_validation_file": "plans/audit-2026-09-23/d03-main-integration-validation.json", + "ci_validation_file": "plans/audit-2026-09-23/d03-ci-validation.json" }, { "audit_id": "A06", @@ -392,18 +394,26 @@ "`lib/presentation/app/bloc/schedule/schedule_bloc.dart:379,387-409,508-514`; `test/presentation/app/bloc/schedule/schedule_bloc_test.dart:1236-1243`", "실제 timer/clock을 전진시켜 단계 변경 알림 1회 검증. resume catch-up 시 중복/불필요 알림 정책도 함께 검증." ], - "status": "ready", + "status": "code_verified_ci_device_pending", "agent": "/root/grill_a06", "issue_number": 598, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/598", "decisions_file": "plans/audit-2026-09-23/issues/A06.md", - "implementation_commit": null, - "pr_url": null, - "verification": [], - "blockers": [], + "implementation_commit": "d06474d4c645794644075173f76afb9ff4445047", + "pr_url": "https://github.com/DevKor-github/OnTime-front/pull/585", + "verification": [ + "Negative actual periodic regression reproduced before fix", + "Final47 boundary and full929 pass; coverage88.74% (13092/14753); analyze/generated/local-only/Python11/manifest pass", + "Seven committed source blobs match validated freeze" + ], + "blockers": [ + "Remote CI pending", + "Actual Android/iOS background/lock/permission delivery pending" + ], "sequence": 12, "readback_verified": true, - "body_sha256": "bc112bdf7b3df15e50484b1813b2897b7ffe31ff5f25cb66872e7fc549528b11" + "body_sha256": "b96545e7bb2a44582be5a68be08798a8c782648d5498236d198335004f1160ec", + "validation_file": "plans/audit-2026-09-23/a06-final-validation.json" }, { "audit_id": "A07", @@ -699,16 +709,18 @@ "상세 알림 토글의 로딩/저장/재등록 실패와 연속 조작을 처리한다.", "`my_page_screen.dart:110-134`: UI 선반영 후 저장, 잠금·rollback·오류 안내 없음. 실패 주입과 빠른 ON→OFF에서 UI/DB/OS가 일치해야 함. A05의 재등록 비교 수정과 함께 처리." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_u03", + "issue_number": 612, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/612", "decisions_file": "plans/audit-2026-09-23/issues/U03.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 25 + "sequence": 25, + "readback_verified": true, + "issue_body_sha256": "c214b2323ca8151f2132e225c65a4d9bd3a679ffc1f90f5f4637747f50bddbe1" }, { "audit_id": "T03", @@ -1539,8 +1551,8 @@ "sequence": 64 } ], - "current_upstream_main": "c7184b2a", - "upstream_integration_status": "latest_figma_merge_ci_pending", + "current_upstream_main": "c7184b2ae477e6a24a47fd0b9da79d3ed52b8cf4", + "upstream_integration_status": "latest_figma_merge_flutter_and_android_passed", "upstream_integration_validation": { "full_tests": 678, "analyze": "no issues", @@ -1554,5 +1566,6 @@ "android_ci_run": 35829634224, "android_apk_identity_file": "plans/audit-2026-09-23/upstream-integration-apk-validation.json" }, - "upstream_integration_commit": "712b37081f95ea30aa6d1c61072d167ddaec78de" + "upstream_integration_commit": "712b37081f95ea30aa6d1c61072d167ddaec78de", + "latest_figma_integration_validation": "plans/audit-2026-09-23/d03-ci-validation.json" } From 7a6825f79a565c9b006f3ec57b837271b0e06ba4 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:13:44 +0900 Subject: [PATCH 40/59] docs: record A06 CI and T03 T04 audit contracts --- CONTEXT.md | 2 +- handoff/audit-2026-09-23-progress.md | 16 +- plans/audit-2026-09-23/README.md | 8 +- plans/audit-2026-09-23/a06-ci-validation.json | 42 +++++ plans/audit-2026-09-23/issues/A06.md | 8 +- plans/audit-2026-09-23/issues/T03.md | 173 +++++++++++++++++ plans/audit-2026-09-23/issues/T04.md | 177 ++++++++++++++++++ plans/audit-2026-09-23/state.json | 32 ++-- 8 files changed, 439 insertions(+), 19 deletions(-) create mode 100644 plans/audit-2026-09-23/a06-ci-validation.json create mode 100644 plans/audit-2026-09-23/issues/T03.md create mode 100644 plans/audit-2026-09-23/issues/T04.md diff --git a/CONTEXT.md b/CONTEXT.md index 7d0ab6ec..d327cabd 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -328,7 +328,7 @@ _Avoid_: Loaded range, stream range, cached range - Backup cryptography uses a reviewed library implementation and never a custom cipher or password-key-derivation construction. - A **Backup Restore** validates the complete OnTime Backup before changing active local data. - A successful **Backup Restore** replaces rather than merges the current Local Profile data. -- A failed **Backup Restore** leaves the current Local Profile data unchanged. +- A **Backup Restore** that fails before durable replacement preserves the current Local Profile data. Failure of cleanup after a committed replacement keeps the restored data active and exposes pending recovery; it does not claim rollback. - An **OnTime Backup** contains all **Durable OnTime Data**, including the Local Profile, onboarding state, Schedules, Places, Preparations, retained outcomes, and app preferences. - An **OnTime Backup** excludes an active Preparation Run, Early Start Session, device identifier, scheduled-notification registry, operating-system permission, cache, and log. - A successful **Backup Restore** recalculates Schedule Notifications from restored future Schedules instead of restoring device-specific registrations. diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 2e8e1d2f..83e79030 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -12,7 +12,7 @@ - DevKor-github/OnTime-front, fix/audit-20260923-stabilization, Draft PR #585 (attach 완료). - 감사 baseline44067d7a. main2b02fb77(PR590 recurring)을712b3708에서 통합했다. schema2/v1 migration, backupformat2/v1 호환, recurring ownership/frozen/exclusions, nearest60을 유지한다. -- 상세 이슈 **21/64개** 게시·exact read-back 완료, **43개 생성 전**. +- 상세 이슈 **28/64개** 게시·exact read-back 완료, **36개 생성 전**. 최신 현황은 아래 2026-09-24 갱신 절 참조. - A01#583 A02#584 A03#586 A04#587 A05#588 A11#589 A12#591 A13#592 A14#593 A15#594 D03#595 U08#597 A06#598 A07#599 C01#600 A08#601 A09#602 D01#603 D02#604 D04#605 D05#606. - A01–A05/A11/A12/A13 자동검증 통과, 기기/후속 통합 조건은 남음. U08 홈 overflow 선행 수정만 포함. A14 구현/CI 후 실제 registry ownership 보존 문제를 A15에서 후속 수정 중. A15는37파일 해시·795 tests/analyze 검증 뒤 ecd04293363fa79bd9cd1b140169619dfc083b96로 커밋했다. - D05까지 이슈별 설계가 확정됐으나 설계 게시와 구현 완료는 다르다. @@ -44,7 +44,7 @@ D04#605: 현재v1→v2가 이미 있다는 사실로 baseline 정정. DDL/versio ## 환경과 주의 - Flutter `/Users/ejunpark/Library/flutter/bin/flutter`, Dart `/Users/ejunpark/Library/flutter/bin/cache/dart-sdk/bin/dart`, gh `/Users/ejunpark/.local/bin/gh`. -- shell은 login:false, sandbox_permissions를 넣지 않는다. Flutter 명령들은 ephemeral plugin 준비 경합을 피하도록 직렬 실행한다. +- shell은 login:false. 네트워크/Git index·ref/Flutter SDK cache에 필요한 경우 승인된 require_escalated를 사용한다. Flutter 명령들은 ephemeral plugin 준비 경합을 피하도록 직렬 실행한다. - generated Dart `*.g.dart`/config/mocks/freezed는 ignored local outputs이며 커밋하지 않는다. 기존 tracked l10n 출력은 변경 ARB와 함께 관리한다. root npm test는 의도적 실패 placeholder이므로 사용하지 않는다. - 디스크 최근6.6GiB. 사용자 파일/다른 worktree를 정리하지 않는다. Android 연결기기 없고 물리iOS는 offline이었다. - 부팅된 iPhone17/iOS26.5 simulator OnTime Recurring QA 20260923 (C8BA159D-A97A-458B-A5FC-B04A8B6F2848)의 upstream QA 앱·데이터는 보존한다. 발견만으로 실제 전달·provider·양방향복원 검증을 통과 처리하지 않는다. @@ -93,3 +93,15 @@ main c7184b2a Figma PR607 충돌을659a5fc4에서해결,복구operation인자오 ## D03 원격 검증 완료 및 A06 전달 D03최종main통합074d5fd6 원격Flutter882/analyze/88.50%,AndroidAPK/manifest통과 artifact10786008849(identity d03-ci-validation.json). A06d06474d4 실제periodic수정commit,47boundary/929full/88.74%,7blob검증완료(a06-final-validation.json),#598본문원격대조완료. U03#612 실제3문답+28case게시26/64,38개생성전. T03전담grill실제3문답확정문서작성중. A07전담구현시작,Flutter명령소유권A07. root는A06docs/push/CI후상위우선구현계속. + +## 2026-09-24 최신 상태 — A06 CI 완료 / A07 구현 중 + +- 원격 HEAD `e6c1438ebcd641386081bb5aa5569e1b7da7ee15`; Draft PR #585 OPEN/MERGEABLE read-back. main `c7184b2ae477e6a24a47fd0b9da79d3ed52b8cf4`의 Figma #607을 통합했다. +- 추가 상세 이슈: A10#608, U02#609, C08#610, U01#611, U03#612, T03#613, T04#614. 실제 전담 grill 문답 전체 및 원격 본문 동일성 확인. 총28/64;36개 생성 전. +- D03 `5f3adf11` 구현, main 통합 후 `074d5fd6` 원격882 tests/analyze·88.50% 및 Android release APK/manifest 성공. 독립 journal, JSON/UTF8 손상 격리, unknown ownership, reset 단계 read-back, host SIGKILL11지점 검증. 기기 조건 미완료. d03-final/main-integration/ci-validation.json 참조. +- A06 `d06474d4` 구현: 실제 periodic→Refresh→NotificationService→channel,47경계/929전체, local88.74%. HEAD e6c1438e 원격 run35945420027은929 tests/analyze·88.79%(13099/14753); Android35945419975 success, artifact10786697671, synthetic0896d865, APKd0f64d4e914bbeb6dcaf7f37e9c62fdaa8f9b35b9b5fc0df673f8fc561c51007. a06-ci-validation.json 참조. #598 OPEN, 기기 pending. +- `/root/implement_a07`가 제품/테스트와 Flutter 실행을 독점하며 진행한다. 실제 DB 원자성/실패 재시도/중복 revision/완료 시작 거부 회귀, commit 전 화면 유지·commit 후 pending 배너, run 시각/유효 snapshot 복구를 구현 중이다. 미커밋 A07을 A06 검증 결과로 간주하지 않는다. +- A07 root의 중간 'DB T0를 모든 새 run에 강제' 제안은 기존 Q2 보충/Q3에 맞지 않아 철회했다. DB 최초 T0는 invariant; 유효한 같은 run T1은 유지; 재확인 후 명시 새 run T2는 독립. owner 교체 자체는 새 run 의도가 아니다. 실제 리뷰/철회도 최종 A07 이슈에 보존할 것. +- A07 root 리뷰 추가: 같은 run의 upcoming stream refresh가 새 ScheduleState.started를 만들 때 pending 배너 상태를 잃지 않아야 한다. 에이전트가 회귀 확인 중. +- `/root/grill_t01` 신규 전담은 고정 e6c1438e 코드로 T01의 실제 문답을 시작했다. docs/T01만 소유하며 Flutter 실행 금지. T04는 문서 완료/게시, T03/T04와 CONTEXT 보완은 다음 docs 커밋 대상. +- 다음 구현 C01→A08→A09→D01→D02→D04→D05→A10→U02→C08→U01→U03→T03→T04→T01→T02… 우선순위/의존성 계획 유지. 전부 완료되지 않았으며 active goal 계속. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 83d1a34a..906d7029 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -27,7 +27,7 @@ | A14 | [#593](https://github.com/DevKor-github/OnTime-front/issues/593) | provider 관측 구현282db21f, 원격772 tests/87.44%·APK 통과, A15 실제 ownership 후속 로컬795 통과·기기 대기 | | A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 요청별 후속 재조정/교체 직렬화 구현, 최종 원격795 tests/analyze·87.66%·APK 통과, 기기 대기 | | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 독립 journal·복구/host kill11, main통합 원격882 tests/88.50%·APK 통과,기기 대기 | -| A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | d06474d4 실제 타이머 전환 구현,47경계/929전체·88.74% 통과,CI·기기 대기 | +| A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | d06474d4 실제 타이머 전환 구현,47경계 및 원격929전체·88.79%·APK 통과,기기 대기 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | 실제3문답·추가 정정 포함, DB 원자적 시작·run 시각·실패 재시도 기준 확정 | | C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 실제3문답·staging/gate 보충 포함, 구체 workflow·부분 결과·통합 의존성 확정 | | A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 실제4문답·원자 저장/조회·충돌/멱등/소유권 기준 확정 | @@ -47,7 +47,11 @@ | U03 | [#612](https://github.com/DevKor-github/OnTime-front/issues/612) | 실제3문답·최신 토글의도/저장-OS반영 구분/화면이탈·세대 경계,28개 검증 시나리오 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **26/64개**를 게시하고 원격 본문 동일성을 확인했으며 **38개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 후 최종872 tests/87.99% 및 host kill11 검증을 통과했고 CI·기기 확인을 진행한다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +| T03 | [#613](https://github.com/DevKor-github/OnTime-front/issues/613) | 실제3문답·실패경계22/암호문12행렬·기존증거 재사용/실제저장소·새프로세스 검증 계약 | + +| T04 | [#614](https://github.com/DevKor-github/OnTime-front/issues/614) | 실제3문답·실제 진입점/독립 oracle·Auth 복구/삭제/세션 연결30개 검증 계약 | + +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **28/64개**를 게시하고 원격 본문 동일성을 확인했으며 **36개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07 실제 DB 시작 결함3개를 재현해 다음 구현 중이다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/a06-ci-validation.json b/plans/audit-2026-09-23/a06-ci-validation.json new file mode 100644 index 00000000..9dbd653e --- /dev/null +++ b/plans/audit-2026-09-23/a06-ci-validation.json @@ -0,0 +1,42 @@ +{ + "head_commit": "e6c1438ebcd641386081bb5aa5569e1b7da7ee15", + "runs": [ + { + "conclusion": "success", + "databaseId": 35945420027, + "event": "pull_request", + "headSha": "e6c1438ebcd641386081bb5aa5569e1b7da7ee15", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35945420027", + "workflowName": "Flutter Testing", + "tests_passed": 929, + "coverage": { + "percent": 88.79, + "lines_hit": 13099, + "lines_found": 14753 + } + }, + { + "conclusion": "success", + "databaseId": 35945419975, + "event": "pull_request", + "headSha": "e6c1438ebcd641386081bb5aa5569e1b7da7ee15", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35945419975", + "workflowName": "Android Notification Contract", + "artifact_id": 10786697671, + "identity": { + "commit": "0896d865d605a9b0966742f9172863b219a6ec5b", + "artifact": "build/app/outputs/flutter-apk/app-release.apk", + "sha256": "d0f64d4e914bbeb6dcaf7f37e9c62fdaa8f9b35b9b5fc0df673f8fc561c51007", + "signing": "disposable CI validation key; not a store release", + "merged_manifests": [ + "build/app/intermediates/merged_manifest/release/outputReleaseAppLinkSettings/AndroidManifest.xml", + "build/app/intermediates/merged_manifest/release/processReleaseMainManifest/AndroidManifest.xml", + "build/app/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml" + ], + "device_delivery_verified": false + } + } + ] +} diff --git a/plans/audit-2026-09-23/issues/A06.md b/plans/audit-2026-09-23/issues/A06.md index ebeadf75..5d00a7d0 100644 --- a/plans/audit-2026-09-23/issues/A06.md +++ b/plans/audit-2026-09-23/issues/A06.md @@ -172,4 +172,10 @@ Legacy Tick.elapsed의 별도 누적을 제거해 같은 wall-derived refresh로 - 제품/테스트7파일을 source freeze 후 재해시했고 root가 커밋blob과도 대조했다. 실제회귀의 fakeAsync 안에서 Bloc.close 대기가 멈춘 하네스 문제는 종료만 tester.runAsync로 분리해 해결했다. 제품 타이머를 가짜 Tick 호출로 대체하지 않았다. - 전체 근거는 `plans/audit-2026-09-23/a06-final-validation.json` 및 `artifacts/a06/`에 보존한다. 원본 로그 해시와 저장용 후행공백 정리를 구별했다. -실제 Android/iOS background·잠금·resume·권한 표시 검증과 이 변경의 원격 CI는 별도 단계다. plugin show 완료는 제출 증거이며 OS에 표시됐다는 보장이 아니다. suspend/kill 동안 periodic 실행이나 모든 단계 정시 전달을 보장하지 않는다. 기기 수용조건이 남아 이슈는 OPEN을 유지한다. +실제 Android/iOS background·잠금·resume·권한 표시 검증은 별도 단계다. plugin show 완료는 제출 증거이며 OS에 표시됐다는 보장이 아니다. suspend/kill 동안 periodic 실행이나 모든 단계 정시 전달을 보장하지 않는다. 기기 수용조건이 남아 이슈는 OPEN을 유지한다. + +## 원격 CI 검증 + +HEAD `e6c1438ebcd641386081bb5aa5569e1b7da7ee15`의 [Flutter run 35945420027](https://github.com/DevKor-github/OnTime-front/actions/runs/35945420027)이 전체 **929개**, analyzer, coverage **88.79% (13099/14753)**로 성공했다. 로컬 coverage와 원격 수치는 각각 기록하며 동일하다고 간주하지 않는다. + +같은 HEAD의 [Android run 35945419975](https://github.com/DevKor-github/OnTime-front/actions/runs/35945419975)도 release APK·merged/packaged manifest 검사를 통과했다. artifact `10786697671`, CI synthetic merge `0896d865d605a9b0966742f9172863b219a6ec5b`, APK SHA256 `d0f64d4e914bbeb6dcaf7f37e9c62fdaa8f9b35b9b5fc0df673f8fc561c51007`을 다시 읽어 확인했다. CI 임시 서명이며 store release나 기기 표시 검증이 아니다. 기계 판독 근거는 `plans/audit-2026-09-23/a06-ci-validation.json`이다. 실제 기기 수용조건과 병합이 남아 OPEN을 유지한다. diff --git a/plans/audit-2026-09-23/issues/T03.md b/plans/audit-2026-09-23/issues/T03.md new file mode 100644 index 00000000..1e10b258 --- /dev/null +++ b/plans/audit-2026-09-23/issues/T03.md @@ -0,0 +1,173 @@ +# [P1][T03] 저장·복원·초기화의 실패 경계와 재시도 수렴을 실제 저장소·암호문·프로세스 증거로 검증 + +상태: 전담 grill 문답 후 실행할 테스트 개선 계획. 이 문서 작성 과정에서는 제품·테스트·도구를 수정하거나 Flutter를 실행하지 않았다. 기존 테스트와 과거 실행 증거는 아래처럼 별도로 식별하며, 미구현 의존 작업을 통과로 표시하지 않는다. + +전담: `/root/grill_t03`. 적용 skill: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +조사 고정 SHA: **`204804e32e1c5f741b3d5644698cd3dde3502592`**. 이는 문서 commit이고 직전 제품 통합은 `074d5fd642a2890dc48933dafcbd038534f759a4`다. 조사 중 A06 제품 commit `d06474d4`가 추가됐으나 그 변경 7개 파일은 아래 인용한 crypto/DB/reset 소스와 테스트를 변경하지 않는다. 최신 A06 테스트·원격 CI를 본 문서의 실행 증거로 주장하지 않는다. + +## 문제와 책임 + +테스트가 없다는 문제가 아니다. 실제 sodium 테스트, profile rollback, 복원과 알람 취소의 경쟁, reset journal 복구 및 host 강제 종료 검증이 이미 있다. 공백은 **제품의 실패 경계와 실제 확보한 증거를 연결하는 공통 행렬**, 그리고 각 작업을 함께 수행할 때의 보존·부분 완료·재시도·세대 교체 증거다. 단순 mock 반환과 호출 횟수, 총 테스트 개수, 전체 coverage만으로 데이터 보존이나 원자적 교체를 증명할 수 없다. + +T03은 각 기능의 테스트를 별도 프레임워크로 복제하지 않는다. A04/A07/A08/A09/D01–D05가 소유하는 제품 수정과 그 이슈의 필수 회귀를 그대로 연결하고, **남은 교차 workflow 실험과 재현 가능한 증거**를 추가한다. 테스트에서 실제 결함을 발견하면 원래 제품 이슈에 연결해 수정하며, 구현의 잘못된 현재 행동을 기대값으로 고정하지 않는다. + +Android/iOS local-only와 기존 schema 2, payload format 1/2, crypto envelope version 1, Preparation Run·Backup Restore·Recovery Mode·Local Data Reset 계약을 유지한다. 서버, 원격 장애 복구, 로그인, cloud test analytics를 추가하지 않는다. + +## 확인한 기존 근거와 한계 + +모든 소스 링크는 조사 고정 SHA를 가리킨다. ‘존재’는 이번 문서 작성 중 소스를 확인했다는 뜻이며 이번에 실행했다는 뜻이 아니다. + +| 기존 근거 | 재사용할 증거와 남은 공백 | +|---|---| +| [backup_crypto_test.dart](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/core/backup/backup_crypto_test.dart) / [sodium loader](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/helpers/sodium_test_loader.dart) | 실제 sodium의 작은 평문 roundtrip, 오답 비밀번호, 마지막 byte 변조 3개가 존재한다. 다중 chunk·header·순서·잘림·final/EOF 조합을 직접 입증하지 않는다. | +| [BackupCrypto](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/lib/core/backup/backup_crypto.dart) | 64 KiB chunk, bounded header/frame, header additionalData, 실제 secretstream을 사용한다. 전부 buffering하고 있어 D05의 bounded streaming 계약을 이미 구현했다고 볼 수 없다. | +| [backup_service_test.dart](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/core/backup/backup_service_test.dart) | 취소·export 오류 후 gate, format 1/2·recurring/exclusion roundtrip, malformed reference·오답 비밀번호 원본 보존이 있다. 전체 multi-stage failure와 실제 암호화 staging/active pair 교체 증거는 아니다. | +| [profile_write_preservation_test.dart](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/data/repositories/profile_write_preservation_test.dart#L222-L245) | 실제 DB trigger로 revision 실패를 만들고 설정/profile rollback 및 재시도를 검사한다. A04 회귀를 복제하지 않고 저장 실패 실험의 기존 근거로 연결한다. | +| [alarm_replacement_integration_test.dart](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/core/database/alarm_replacement_integration_test.dart) | 실제 BackupService/ResetService와 in-memory Drift, 제어된 native 응답을 연결한다. 늦은 OS 응답을 기다린 restore, 취소 실패 시 원본 보존/재시도, reset 후 ownership 보존·필요한 단계 재시도가 있다. 실제 모바일 OS 증거는 아니다. | +| [alarm_reconciliation_concurrency_test.dart](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart) | A15 cutoff·지연 Future·generation invalidation과 후속 요청 처리를 검증한다. fake의 제품 알고리즘 복사가 아니라 실제 workflow와 경계 adapter를 계속 사용한다. | +| [local_reset_protocol_test.dart](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/core/database/local_reset_protocol_test.dart), [alarm_journal_recovery_test.dart](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/core/database/alarm_journal_recovery_test.dart) | legacy marker·uncertain ownership, intent/write/readback, partial cleanup, syntax corruption·unsupported/I/O 구별, fresh owner 복구가 이미 있다. 동일 테스트를 새 폴더에 복사하지 않는다. | +| [D03 final-validation](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/plans/audit-2026-09-23/d03-final-validation.json) | 해당 source freeze에서 872 전체 테스트·87.99% 및 11 host SIGKILL 증거가 기록되어 있다. 구현 commit은 `5f3adf11aaaa3e36d6e4e25c41be6c06e4105480`. 최신 HEAD·모바일·전원 손실 검증으로 확대하지 않는다. | +| [host kill 도구](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/tool/qa/verify_reset_crash_recovery.py), [결과](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/plans/audit-2026-09-23/artifacts/d03/final/host-process-kill/result.json) | 자체 temp 디렉터리·새 프로세스에서 11 checkpoint를 검사한다. provider/deletion adapter는 fixture이고 손상 bytes는 준비한 입력이다. write syscall 중단/기기 전원 손실/실제 OS 알람 검증이 아니다. | +| [recurring_migration_test.dart](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/core/database/recurring_migration_test.dart) | 현재 DB에서 v2 추가물을 역제거한 v1 파일로 실제 onUpgrade 성공을 검증한다. 독립 historical v1, SQLCipher, migration failure·commit 원자성 증거는 D04에서 보강해야 한다. | +| [startup recovery widget](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/presentation/startup/local_reset_recovery_test.dart), [restore widget](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/test/presentation/my_page/my_data_restore_test.dart) | reset partial/complete/retry와 preview 취소·picker 취소 경로가 있다. D02 candidate activation 전체 경로가 구현됐다는 뜻은 아니다. | +| [Flutter CI](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/.github/workflows/flutter_test.yml) | Flutter 3.44.4, native sqlite/sodium 설치, generator·analyze·full tests·coverage gate가 이미 있다. 또 다른 중복 test runner를 만들 이유가 없다. | + +## 도메인 판정 기준 + +[CONTEXT](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/CONTEXT.md), ADR 0013/0023/0026/0028/0029/0031/0032 및 [ADR0036 active pair](https://github.com/DevKor-github/OnTime-front/blob/204804e32e1c5f741b3d5644698cd3dde3502592/docs/adr/0036-select-recovery-store-and-key-as-one-active-pair.md)를 따른다. 여기서 commit 경계·fault point·evidence level은 검증 설명이며 새 제품 용어가 아니다. 새 glossary/ADR는 필요하지 않다. + +- **commit 이전 실패**: 작업의 소유 범위에 대한 durable 상태가 바뀌지 않았음을 실제 read-back한다. 준비 시작 실패면 화면/버튼과 같은 의도 재시도도 확인한다. 미리 수행한 delivery cleanup이 있다면 그것을 ‘아무 부작용도 없었다’고 숨기지 않고 원래 기능의 복구 계약을 검사한다. +- **commit 이후 후속 실패**: 이미 확정된 시작/복원/삭제 사실을 실패 반환만 보고 rollback이라고 표시하지 않는다. typed partial/pending과 필요한 단계만 재시도하는 계약을 확인한다. +- **애매한 응답**: write가 오류를 반환하거나 receipt가 없어도 독립 read-back에 확정된 사실이 있으면 그 사실을 따른다. 단순 예외 유무는 저장소의 oracle이 아니다. +- **재시도 수렴**: 같은 service/의도에서 재시도, 새 instance에서 재개, 실제 새 process에서 재개를 구별한다. 한 번의 의도가 중복 row/revision/run/navigation/OS 등록을 만들지 않고 최신 committed 결과로 수렴해야 한다. +- **세대 전환**: reset/restore 이후 같은 ID를 가진 새 데이터에 이전 Future/flight/snapshot/notification 결과가 적용되지 않는다. Future를 무시했다고 실제 native 작업이 취소됐다고 주장하지 않는다. +- **보존 관측**: durable rows/관계·schema/version·revision·runtime generation·active pair·비노출 key identity·registry/journal ownership·사용자 화면을 해당 경계에 맞춰 함께 본다. SQLite/WAL 파일 전체 byte hash 동일을 모든 rollback의 조건으로 삼지 않는다. +- **비밀 보호**: 합성 fixture만 사용한다. raw 비밀번호·키·복호문·사용자 note/payload·실제 경로가 예외/UI/log/artifact에 노출되지 않았음을 확인한다. 디버그 편의를 위해 원본 정보를 로그에 더하지 않는다. + +## 공통 실패 경계 행렬 + +각 행은 고정 source/test 또는 실행 artifact에 매핑한다. 아래는 요구하는 행동이며 실행 통과 목록이 아니다. 기존 회귀로 충분한 행은 링크로 충족시키고, 부족한 조합만 보강한다. + +| 구분 | 실험과 기대 결과 | 원래 제품 책임 / T03 추가 책임 | +|---|---|---| +| F01 | A04 revision write 실패→profile/setting/score 원본 유지→fault 제거 후 같은 instance 재시도 성공 | A04 #587 기존 trigger/read-back 재사용. 다른 workflow가 이 불변식을 깨는 경우만 추가. | +| F02 | 준비 시작 첫 write·revision write 실패, 두 동시 caller pending/공통 실패, 같은 run 재시도 | A07 #599 제품 transaction/singleflight. 실제 DB 상태·최초 durable 시각·run 시각·revision과 widget 이동을 함께 확인. | +| F03 | 시작 commit 후 marker/snapshot/OS 취소 실패, 그동안 skip/finish, restore/reset과 경쟁 | A07/A06/A15/D03. 최신 action events 보존, 완료 run 부활/새 generation 쓰기 없음, 부분 오류가 새 run을 만들지 않음. | +| F04 | 일반 일정+장소+준비 첫/중간/마지막 write·revision 실패, rollback 후 동일 초안 재시도 | A08 #601. orphan·성공 stream·부분 알람 계획이 없고 한 commit/한 revision. 실제 reader의 중간 관측 포함. | +| F05 | 같은 의도 duplicate/concurrent, receipt 유실 후 새 instance, 다른 내용 same ID, delete/recreate/ABA | A08. durable 근거로 멱등/충돌 판정하며 오래된 성공이 새 aggregate를 덮지 않음. | +| F06 | 반복 create/이번 회차/이후 회차 split·materialize·exclusion·owned preparation·revision 중간 실패 | A08. 기존 반복 transaction을 사용하며 frozen/history·공유 template/default가 보존됨. | +| F07 | malformed/오답/지원 밖 backup→preview 0/apply 0→같은 service로 정상 file 재시도 | A09/D05. 활성 데이터/key/runtime/ownership 보존과 gate 재사용을 workflow까지 연결. | +| F08 | verified preview 취소·만료·새 revision·generation 변경, 늦은 validation 완료 | A09 #602/C01. 현재 의도만 preview/apply 가능하며 취소/과거 후보가 활성 자료를 교체하지 않음. | +| F09 | 정상 restore staging insert/관계/검증 실패, 활성 DB transaction 중간/commit 응답 실패 | A09. commit 전 원본 보존, commit 이후 read-back 사실을 판정; 런타임 세대 전환과 후속 cleanup을 구별. | +| F10 | 같은 ID/fingerprint 복원과 old timer/early-start/runtime write 경쟁 | A09/A15/A07. backup에 active session이 없고 old generation이 새 데이터의 run을 자동 생성하지 않음. | +| F11 | startup key/file/cipher/migration 실패→제한 화면→중복 retry/timeout/late completion | D01 #603. 기존 store/key 보존, getOrCreate로 실패 숨김 없음, 단일 attempt와 정상 화면/알람 차단. | +| F12 | 손상 DB에서 후보 검증 실패/activation 전 cancel, 후보 key와 DB 준비 중 실패 | D02 #604/D05. 정상 DB를 요구하지 않고 원래 pair 유지; 후보만 안전 정리, plaintext 임시 유출 없음. | +| F13 | active manifest/journal write·rename·readback 전후 process 종료, 모호한 응답·손상 manifest | D02. 재시작에서 검증된 한 pair 또는 제한 Recovery, 기존 pair/키를 임의 삭제/새 설치로 오인하지 않음. | +| F14 | candidate activation commit 후 기존 pair cleanup 실패→재실행→cleanup retry | D02. 새 pair 유지, 거짓 rollback 금지; 재열기 검증 전 old pair 삭제 금지. | +| F15 | reset intent/marker/journal 기록 실패와 pending ownership, DB/prefs/key/launch 정리 실패 | D03 #595 기존 protocol/actions/recovery 테스트 재사용. explicit reset과 일반 restore 실패의 보존 기대를 혼동하지 않음. | +| F16 | reset 완료 receipt 뒤 late retry, 손상 journal 재구성/관측 실패 후 fresh owner | D03 기존 회귀 재사용. 새 데이터를 다시 지우지 않고 unknown ownership을 빈 성공으로 표시하지 않음. | +| F17 | D03 11 host kill checkpoint | 기존 harness/result 재사용. 제품/provider 경계가 바뀐 경우 필요한 재실행과 새 SHA 기록; 같은 내용을 복제하지 않음. | +| F18 | 독립 historical v1 migration의 DDL/version/commit 경계 실패 | D04 #605. 독립 연결 재열기에서 온전한 v1 또는 온전한 v2, 부분 schema/version 성공 금지. | +| F19 | 실제 SQLite FULL/read-only/locked/constraint, wrong key·missing cipher | D04. 실제 관측 code/engine/fixture 기록; adapter throw를 native 오류라고 표시하지 않음. | +| F20 | migration 후 close/reopen/재시도, unsupported future/partial version0 | D04. 데이터/관계/version/revision 보존, 실패를 empty DB 생성으로 회피하지 않음. | +| F21 | save/start→export snapshot→restore/reset 동시 요청의 선택된 순서 조합 | A07/A08/A09/A15/D03. cutoff/gate/currentness 계약으로 수렴, DB lock 안에 OS await 추가 금지, unreturned 작업의 owner 조기 해제 없음. | +| F22 | 실패/부분 완료/재시도 버튼의 실제 widget 경로 | C01/D01/A07/U03. 오류 상태를 실제 action→typed receipt→표시/이동까지 연결; 이벤트만 주입해 사용자 재시도를 생략하지 않음. | + +## 암호문·parser 실험 행렬 + +직접 만든 암호 primitive나 fake crypto 성공값으로 인증을 검증하지 않는다. 실제 sodium과 공개 제품 경로를 사용한다. header/frame를 변형하는 테스트 helper는 container 구조를 읽고 입력을 만드는 도구이며 별도의 decrypt 구현이나 성공 oracle이 아니다. + +| 구분 | 입력/변형 | 기대 및 관측 | +|---|---|---| +| C01 | 평문 0/1/64 KiB−1/64 KiB/64 KiB+1/여러 chunk | 실제 encrypt→decrypt byte 동등. 빈 평문 crypto 성공을 유효 Backup payload 승인과 혼동하지 않음. | +| C02 | magic 변경/잘림, header length 0/상한/상한 초과, length prefix 잘림 | 구조상 invalid/unsupported 입력은 KDF 이전 거부. 상한 값 자체가 곧 유효 JSON/header라는 뜻은 아니므로 정상 조건과 분리. | +| C03 | header JSON 타입/누락/중복 key/잘못된 UTF-8·escape, suite/version/KDF/chunkSize/salt 타입·길이 | 안전한 parameter whitelist를 우회하지 않음. D05 parser 규칙 적용 전/후를 분리하고 아직 미구현이면 blocked dependency로 표시. | +| C04 | 지원 범위 안의 header 값/salt 변경과 stream header 변경 | 정상 지원 입력을 거치는 실제 sodium 인증 실패. 단순 validator 거부만으로 AAD 결합을 증명하지 않음. | +| C05 | frame count 0/1/상한 초과·count 불일치, frame length 0/상한 초과·length prefix 잘림 | 과도 할당/KDF 전에 가능한 구조 검증. count/length 경계가 실제 처리된 것을 기록. | +| C06 | 중간/첫/마지막 ciphertext 변조, frame 순서 교환·복제·누락, 다른 stream의 frame 삽입 | 실제 인증/순서 검증 실패. 같은 마지막 byte 변경만 여러 이름으로 반복하지 않음. | +| C07 | 각 구조 경계 잘림, 마지막 frame/final tag 부재, final 이전 stream 종료 | authenticated final/전체 EOF 조건 없이는 preview ready 없음. 라이브러리가 자동 거부하는지 실제로 관측하고 수용 결함이면 제품 수정 연결. | +| C08 | 조기 final 뒤 추가 frame, 두 final, 정상 EOF 뒤 garbage/연결된 두 파일 | trailing/다중 stream을 묵시적 무시하지 않음. final tag 자체를 직접 byte 변조한 것과 유효하게 만들어진 조기 final stream을 구별. | +| C09 | D05 encrypted/plain bytes·depth·string/token·record/work budget의 경계 | D05 정책 상수를 사용하며 사양 기대를 독립 확인. cap−1/cap/cap+1, unknown/거짓 length·읽는 중 증가 검증; 과도한 실제 할당 없이 admission 경계 주입. | +| C10 | provider chunk 분리: 1 byte, UTF-8/escape/length/frame 경계 split | chunk 크기에 따라 검증 결과가 달라지지 않고 malformed encoding을 replacement로 수용하지 않음. | +| C11 | 인증은 정상이나 format/참조/preparation graph/timezone/recurrence 자료가 부적합 | D05 전체 검증에서 거부 또는 정해진 사용자 시간대 해결 상태. crypto 성공만으로 ready/apply하지 않음. | +| C12 | 각 실패 동치군→같은 instance 정상 파일 재시도 | preview/apply 0, 활성 rows/key/runtime/ownership/revision 원본 보존, gate/resource 해제, 오류의 민감 원문 미노출, 정상 재시도 성공. | + +비싼 KDF를 모든 변형마다 다시 생성할 필요는 없다. 검증된 합성 encrypted fixture를 재사용하고 동일 속성 동치군을 표본화할 수 있으나 행렬과 실제 case의 매핑을 남겨 누락을 숨기지 않는다. KDF 이전 거부는 loader/derive boundary의 spy wrapper로 관측할 수 있으나 유효한 지원 입력의 암호학적 실패는 실제 sodium을 사용한다. fuzz/property를 도입하면 고정 seed·bounded 실행·최소 실패 fixture·재현 명령을 기록하며 무한 fuzz나 통과할 때까지 재실행하지 않는다. + +D05의 72 MiB ciphertext, 64 MiB plaintext, depth 32, schedule 100,000/total 500,000 records, steps 1,000, work budget 2,000,000 및 하위 문자열/숫자 규칙을 이 이슈에서 재정의하지 않는다. 실제 모바일 peak memory/시간의 증명은 별도 모바일 증거다. 작은 주입 한도 테스트만으로 제품 상한에서의 실제 메모리 동작을 통과로 보고하지 않는다. + +## 증거와 실행 방식 + +1. 각 행에 제품 이슈·현재 구현 상태·test path/name·fixture 출처/hash·source SHA·fault 지점·실제 오류·기대 불변식·결과 artifact·남은 한계를 기록한다. 상태는 기존 증거 충족/추가 실험 필요/제품 의존 미구현/실행 실패/모바일 미검증을 구별한다. +2. 동일 프로세스 adapter fault, 실제 SQLite 오류, fresh-instance, host SIGKILL, Android/iOS 재실행을 별도 증거 수준으로 표기한다. native mock 또는 plain SQLite 성공을 SQLCipher·실제 OS 성공으로 부르지 않는다. +3. 실패 전후 실제 저장소를 읽는다. logical rows/schema/version/revision 및 비노출 key identity가 oracle이다. fixture/hash는 재현성과 입력 보존을 위한 것이며 WAL/checkpoint에 따른 물리적 바이트 차이를 논리 rollback 실패로 오인하지 않는다. +4. 동시성은 Completer/latch·제어 clock·해당 단계 read-back으로 도착 순서를 정한다. 임의 sleep에 의존하지 않고 bounded watchdog은 hang을 실패로 보고한다. timeout 자체로 취소·commit/rollback·owner 해제를 추정하지 않는다. +5. host kill은 자체 생성한 temp 디렉터리와 자식 프로세스만 종료한다. D03의 완료 checkpoint 11개를 기반으로 D02 active pair와 D04 migration의 새 경계만 추가한다. 기존 사용자 DB·키·부팅된 simulator·기존 앱 데이터를 사용하지 않는다. +6. synthetic fixture에 정상/오류 구조, version, source provenance를 남긴다. 역사 fixture는 현재 serializer가 만든 자료만으로 대신하지 않는다. 비밀번호/키는 테스트 전용이며 artifact에 원문을 내보내지 않는다. +7. 빠른 결정적 회귀는 기존 `flutter test`/PR CI에 포함한다. 지원 runner에서 host kill은 명시 job/명령·timeout·로그·result를 제공한다. CI wiring은 T01/R 관련 이슈, 모바일 반복 실행 기반은 T02와 연결하고 중복 workflow/framework를 만들지 않는다. +8. 전체 실행 SHA와 CI run/check 결과를 새로 기록한다. 선행 D03 872/host11이나 A06 결과를 다른 source에 재사용하는 경우 변경 영향과 한계를 표시한다. 실패 후 임의 skip/pass, 과도한 retry, coverage threshold 완화로 결과를 바꾸지 않는다. + +## 의존성 및 완료 조건 + +- 제품 변경: [A04 #587](https://github.com/DevKor-github/OnTime-front/issues/587), [A07 #599](https://github.com/DevKor-github/OnTime-front/issues/599), [A08 #601](https://github.com/DevKor-github/OnTime-front/issues/601), [A09 #602](https://github.com/DevKor-github/OnTime-front/issues/602), [D01 #603](https://github.com/DevKor-github/OnTime-front/issues/603), [D02 #604](https://github.com/DevKor-github/OnTime-front/issues/604), [D03 #595](https://github.com/DevKor-github/OnTime-front/issues/595), [D04 #605](https://github.com/DevKor-github/OnTime-front/issues/605), [D05 #606](https://github.com/DevKor-github/OnTime-front/issues/606). +- 공통 lifecycle/delivery: [A15 #594](https://github.com/DevKor-github/OnTime-front/issues/594), [C01 #600](https://github.com/DevKor-github/OnTime-front/issues/600), A06/A10/U03의 해당 사용자 흐름을 유지한다. +- D02/D05의 아직 미구현 경계를 fake-only 모델로 먼저 실험할 수는 있지만 제품 연결과 실제 storage 증거 전에는 blocked dependency로 남긴다. D04 actual cipher/migration과 모바일 조건도 원래 이슈의 수용 기준에서 삭제하지 않는다. +- T01/R CI·T02 mobile 실행 기반·T06 성능은 연결 의존성이다. 공통 테스트 이슈로 넘겼다는 이유로 제품 이슈의 필수 검증을 면제하지 않는다. + +- [ ] F01–F22/C01–C12의 각 행동이 기존 증거 또는 새 실험에 매핑되고, 미구현·미실행 상태가 표시된다. 항목 수는 테스트 개수 목표가 아니다. +- [ ] 제품 계약에서 도출한 실패 기대가 실제 workflow와 저장소 read-back으로 증명된다. fake 내부에 제품 알고리즘을 복사하지 않는다. +- [ ] commit 전 실패·commit 후 partial·애매한 응답을 올바르게 구별하며 같은 의도 재시도/새 instance/세대 교체에서 중복 부작용이 없다. +- [ ] 실제 sodium multi-chunk·header/frame/final/EOF 실패와 D05 bounded parser 통합의 누락 경계가 검증된다. +- [ ] D03 기존 host kill 증거를 유지하고 D02/D04 새 중단 경계를 필요한 저장소/프로세스로 검증한다. power-loss 또는 실제 모바일 OS 증명으로 과장하지 않는다. +- [ ] 실제 SQLite 오류·역사 fixture·SQLCipher·Android/iOS 재실행은 해당 증거 수준으로 기록된다. 요구된 mobile end-to-end 실패→보존→재시도 증거가 없으면 그 조건은 미완료다. +- [ ] UI/API 오류와 logs/artifacts에 비밀번호/키/원문이 노출되지 않고 failed attempt의 handle/isolate/gate가 안전하게 정리되거나 명시적 pending 상태로 유지된다. +- [ ] 기존 CI에서 analyzer/관련 회귀/full tests와 필요한 host job의 정확한 SHA·명령·환경·로그를 확인한다. 새 테스트 수/coverage 상승을 성공 기준으로 대체하지 않는다. +- [ ] 발견한 제품 결함을 원래 이슈에 연결해 수정하고, 미실행 의존 조건을 남긴 채 T03를 닫지 않는다. + +제외: 범용 retry/chaos 플랫폼 구축, 새 암호 방식, 전체 제품 rewrite, 실사용 데이터 파괴 실험, 모바일 전원 손실 절대 보장, 실제 사용자 기기 초기화, 커버리지 수치만 높이는 테스트, generated Dart 커밋. + +## grill-with-docs 실제 전체 문답 + +전담 에이전트가 skill·기존 문서·고정 소스·저장된 실행 증거를 읽고 root에 한 번에 한 질문을 보냈다. 아래는 권고를 포함한 실제 질문과 사용자를 대리한 root 답변 원문이다. 띄어쓰기 없는 부분은 원문을 보존했다. + +### Q1 — 공통 테스트 이슈와 제품 이슈의 책임 + +**질문 원문** + +> T03 Q1 — 기존 증거는 충분히 구분할 수 있습니다. backup_crypto_test는 실제 sodium의 작은 payload roundtrip·오답 암호·마지막 byte 변조 3개이고, D03은 journal/실제 프로토콜·11회 host SIGKILL 증거가 이미 있습니다. A04에는 revision 실패 rollback, A15에는 지연된 OS 응답/세대 전환 테스트도 있습니다. T03의 완료 범위를 ‘기존 개별 이슈 회귀를 다시 작성’이 아니라 공통 실패 경계 목록→기존 증거 연결→누락된 교차 workflow 실험으로 확정할까요? 권고: A07/A08 DB commit 전후, A09/D02 restore staging→active pair 전환→후속 cleanup, D03 reset journal, D04 migration, D05 parser/crypto를 한 매트릭스에 묶되 각 제품 수정은 원래 이슈가 소유하고 T03은 실제 저장소 read-back·다음 호출 재시도·새 instance 재개·old generation 결과 폐기의 조합과 검증 증거를 소유합니다. 단순 테스트 수/coverage 증가를 완료 기준으로 삼지 않고, 미구현 D02/D05 동작을 현 테스트가 통과했다고 추정하지 않겠습니다. + +**root 답변 원문** + +> Q1채택. T03는공통실패경계매트릭스와누락된crossworkflow실험/재현가능증거소유,제품수정은각원래이슈연결. 기존D03host11/A04rollback/A15latefuture회귀그대로링크재사용,동일원리테스트복제안함. 실패점마다commit전무변경/commit후부분결과/재시도수렴/새instance복구/구generation폐기 중해당기대명시. 실제저장소readback와민감원문부재·revision·OSownership확인, 반환값만성공판정금지. DI/repofake속알고리즘복사말고실제workflow+경계adapterfault사용. coverage숫자/개수목표없음, fault주입전후원본snapshot/digest/readback으로증명. 아직미구현D02/D05는blockeddependency로표시,모델test와제품연결/실제host/mobileproof구분. 모바일OS·powerloss못증명한것은미검증그대로남김. + +### Q2 — 실제 암호화 및 입력 검증 실패의 oracle + +**질문 원문** + +> T03 Q2 — 암호문 실패 실험의 oracle과 한도를 확정하겠습니다. ADR0031은 final tag·순서·잘림·trailing chunk 거부를 요구하지만 현재 3개 테스트는 이를 직접 검증하지 않습니다. 권고: 실제 sodium과 합성 payload로 0/1/chunkSize−1/chunkSize/chunkSize+1/다중 chunk의 정상 roundtrip을 먼저 고정하고, magic/header 길이·JSON/UTF-8·suite/version/KDF/salt/stream header·frame count/length·frame 내용/순서/중복/누락/잘림·final 부재/조기 final 뒤 추가 data/EOF 뒤 garbage를 체계적으로 변형합니다. 지원 밖 header/길이는 KDF 호출 전 거부를 관찰하고, 지원 범위 내 header 변조는 실제 인증 실패를 관찰합니다. 실패마다 preview ready/apply 0, 활성 DB/key/runtime/ownership·revision 보존, 실패 뒤 정상 파일로 같은 서비스 재시도 성공까지 연결합니다. D05 cap/depth/string/record/work-budget 값은 재정의하지 않고 cap−1/cap/cap+1 및 chunk split 회귀를 공유하며, actual KDF 테스트 비용은 한 fixture를 재사용하되 mock crypto로 인증 경계를 대체하지 않습니다. 이 범위로 확정할까요? + +**root 답변 원문** + +> Q2채택. 실제sodium roundtrip경계와프레임변형행렬/실패뒤정상재시도,preview/apply0및활성데이터/key/runtime/ownership/revision불변확인. KDF전거부는호출계수/spywrapper로관측하되유효지원입력의암호학적실패는진짜sodium사용. 모든실패case마다고비용KDF재생성할필요없고검증된합성암호문fixture재사용/동일속성동치군표본허용,검사누락을숨기지말고행렬매핑명시. D05한도값재정의없음,cap경계기대는공유정책상수에서읽되기대행동은사양으로독립검증. fuzz/property쓰면고정seed/최소실패fixture재현,무한퍼즈/과도한실제메모리할당없음. 모바일실제파일picker와호스트암호화단위·workflow검증구별. 직접암호문파싱오류가민감raw본문/패스워드로그로노출되지않는회귀포함. + +### Q3 — 증거 수준, 프로세스 중단, CI와 완료 경계 + +**질문 원문** + +> T03 Q3 — 재현 증거와 CI/실기기 완료 경계를 확정하겠습니다. 권고: 고정 SHA + fixture provenance/hash + fault 지점·실제 관측 오류 + 독립 reader의 전후 논리 snapshot + 재시도/새 프로세스 결과를 각 행에 남기고, SQLite/WAL은 전체 파일 byte hash 동일 대신 schema/rows/version/revision·키 identity를 판정합니다. adapter throw, 실제 SQLite FULL/read-only/locked/constraint, fresh-instance, host SIGKILL, Android/iOS 프로세스 재실행은 별도 evidence level로 표기합니다. 기존 D03 host 11은 재사용하고 D02 active manifest와 D04 migration의 새로운 commit/rename 경계만 자체 소유 temp fixture 프로세스로 추가하며 전원 손실·모바일 알람 보장으로 확대하지 않습니다. 빠른 결정적 회귀는 기존 PR CI에 포함, 실제 host kill은 지원 runner에서 별도 명시 job/명령과 artifact를 제공하고 중복 runner/framework를 만들지 않습니다. CI wiring은 T01/R 계열, 모바일 반복 실행 기반은 T02와 연결하되 T03의 선택된 mobile end-to-end 실패→보존→재시도 조건은 그 증거가 없으면 미완료로 남깁니다. slow 단계 임의 sleep·skip/pass·재실행 운으로 숨기지 않고 latch/clock과 bounded watchdog으로 검증하며, 실제 사용자 데이터/기기는 건드리지 않습니다. 이 검증·종결 범위로 확정할까요? + +**에이전트 근거 보충 원문** + +> T03 근거 보충: 현재 HEAD가 d06474d4로 이동한 것을 확인했습니다. 조사 본문은 204804e32e1c5f741b3d5644698cd3dde3502592로 고정하며, A06 차이 7개 파일은 T03에서 인용한 crypto/DB/reset 테스트를 변경하지 않음을 read-only diff로 확인했습니다. D03의 872/host11은 저장된 final-validation의 해당 소스 증거로만 인용하고 A06 전체 테스트 결과나 최신 HEAD CI로 확대하지 않겠습니다. 기존 CONTEXT의 ‘failed restore unchanged’는 pre-commit 실패에 적용하고, post-commit cleanup 오류는 A09/D02의 committed+pending 계약에 따라 허위 rollback으로 표현하지 않는 행을 넣겠습니다. + +**root 답변 및 보충 채택 원문** + +> Q3권고채택. SQLite/WAL파일byte동일을불변식으로오인하지말고독립reader logicalsnapshot/schema/rows/revision/keyidentity로판정,파일존재/활성manifesthash는그항목의계약일때만사용. actualFULL/readonly/locked는isolatedfixture에서실제driver실패유도하는명령과관측오류기록,플랫폼지원불가면skip=미검증으로표시·adapterthrowpass로승격금지. hostkill기존D03재사용, D02/D04신규경계는해당제품실현후기능별추가,sharedharness우선. watchdog은hang검출/프로세스정리용이지제품owner해제아님. CI별도job범위T01/R중복안함. mobile필수선택case증거없으면OPEN,나머지진행계속. 고정SHA 및A06차이구별보충채택. CONTEXT failedrestore문구가precommit전용이라해석되는정합문구필요하면현재문장+최소수정제안; 새ADR필요없음. 실제3문답+보충전부포함문서동결해주세요. + +**에이전트 CONTEXT 최소 정정 제안 원문** + +> CONTEXT 최소 정정 제안: 현재 L331 `- A failed **Backup Restore** leaves the current Local Profile data unchanged.`를 `- A **Backup Restore** that fails before durable replacement preserves the current Local Profile data. Failure of cleanup after a committed replacement keeps the restored data active and exposes pending recovery; it does not claim rollback.`로 좁히면 A09/D02의 확정 계약과 일치합니다. 이 서브에이전트는 공유 문서를 수정하지 않고 T03에 제안과 근거만 기록합니다. Q3 보충까지 수령했으며 전체 실제 문답을 포함해 본문 동결하겠습니다. + +**root 최종 문서 정합화 답변 원문** + +> CONTEXT최소정정채택및root반영완료. 제안문장그대로pre-durable replacement보존/committed후cleanup pending은복원데이터active·rollback주장금지로정합화했습니다. 이실제최종응답도문답보충에포함해주세요. source문서동결후hash/길이/EOF/placeholder/링크확인보고. diff --git a/plans/audit-2026-09-23/issues/T04.md b/plans/audit-2026-09-23/issues/T04.md new file mode 100644 index 00000000..b27cbe10 --- /dev/null +++ b/plans/audit-2026-09-23/issues/T04.md @@ -0,0 +1,177 @@ +# [P1][T04] 실제 사용자·타이머·프로필 이벤트 경로를 검증하고 제품 계약과 어긋난 테스트 기대값·복제 알고리즘 교체 + +## 문제와 결과 목표 + +기존 테스트 중 일부는 실제 앱이 사용하지 않는 이벤트를 직접 주입하거나 상태 객체만 생성한다. 또 완료 일정의 삭제를 숨기는 현재 동작을 기대값으로 고정하고, 테스트 fake가 생산 코드의 준비 진행 복원과 prompt 판정 알고리즘을 다시 구현한다. 이런 테스트가 통과해도 사용자 진입점과 실제 workflow가 연결되어 있는지, 실패 후 데이터가 보존되는지 증명할 수 없다. + +T04는 **제품 계약에서 독립적으로 도출한 기대 결과를 실제 진입점→제품 workflow→관측 가능한 상태·저장소·외부 경계까지 연결**한다. 기능 변경은 원래 제품 이슈와 함께 수행하고, 그 변경을 재현하는 회귀·잘못된 oracle 수정·테스트 이중 구현 제거를 담당한다. 기존에 유효한 상태/equality/helper 테스트와 단순 I/O fake를 무조건 없애지 않는다. 삭제한 테스트 수나 coverage 상승 자체가 목표가 아니다. + +- 우선순위: **P1**, 감사 항목 T04. +- 조사 기준: **`e6c1438ebcd641386081bb5aa5569e1b7da7ee15`**. 아래 소스 근거는 이 고정 커밋의 내용이다. +- 병행 A07 작업은 preparation use case, Bloc 및 일부 테스트를 변경하고 있다. 이슈 구현 시 그 확정 API/receipt와 증거를 재사용하며, 작업 중인 파일을 고정 커밋의 구현·검증 결과로 인용하지 않는다. +- 이 본문은 실제 3문답으로 확정한 계획이다. 이번 문서 작성에서 제품 코드 수정이나 Flutter 테스트를 실행하지 않았다. + +## 코드·문서 근거와 기존 증거 + +| 고정 근거 | 확인한 내용과 T04의 처리 | +|---|---| +| [감사 T04](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/App-Improvement-Audit-2026-09-23.md#L117) | 실제 scheduler, Auth 이벤트/복구 전이, 삭제 숨김 기대, 생산 알고리즘 복제 fake가 감사 대상이다. | +| [auth_bloc_test.dart](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/app/bloc/auth/auth_bloc_test.dart) | 현재 두 테스트는 AuthState 생성 결과만 확인한다. AuthBloc 생성·요청 이벤트·load/privacy/stream 처리·후속 구독은 실행하지 않는다. 유효한 상태 테스트는 남기고 연결 증거를 추가한다. | +| [AuthBloc:25](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/presentation/app/bloc/auth/auth_bloc.dart#L25) | AuthUserSubscriptionRequested가 load→privacy→stream을 연결한다. load/privacy 실패는 recovery지만 stream onError는 addError뿐이다. onData는 zero-delay await 뒤 현재 status만 보고 후속 요청을 수행한다. | +| [App:30](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/presentation/app/screens/app.dart#L30), [lifecycle bridge:152](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/presentation/app/screens/app.dart#L152) | App 생성이 실제 Auth 이벤트를 보낸다. 앱 lifecycle은 ScheduleBloc 관측과 resume refresh, launch payload, reconciliation으로 이어진다. 상태를 만든 것과 이 bridge를 실행한 것을 구별한다. | +| [router recovery:302](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/presentation/shared/router/go_router.dart#L302) | recovery 경로 분기가 있다. 실제 Auth 전이가 router에 전달되는 별도 연결 검증이 필요하다. | +| [schedule_detail_test:287](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/calendar/component/schedule_detail_test.dart#L287), [완료 삭제 숨김:315](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/calendar/component/schedule_detail_test.dart#L315) | 진행 중 삭제 액션 1개, 완료 이력 액션 0개를 기대한다. U01의 실제 진행 중 삭제 차단·완료 이력 개별 삭제 계약과 충돌한다. 제품 수정과 함께 기대값을 바꾸고 실제 삭제 결과까지 확인한다. | +| [FakeSchedulePreparationSessionUseCase:206](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/app/bloc/schedule/schedule_bloc_test.dart#L206), [replay 복제:399](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/app/bloc/schedule/schedule_bloc_test.dart#L399) | fake가 초기화, 시간 경과, action 정렬/replay, skip 및 prompt 판정을 직접 구현한다. 같은 결함을 이중으로 숨길 수 있어 실제 use case와 경계 adapter로 바꾼다. | +| [marker 수동 복구:1529](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/app/bloc/schedule/schedule_bloc_test.dart#L1529) | fake는 invalid state를 삭제하고 production은 safe marker를 보존한다는 차이를 주석으로 인정한 뒤, 테스트가 marker를 다시 넣는다. 이 보정은 실제 보존/재시작 동작을 증명하지 않는다. | +| [실제 session use case](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/domain/use-cases/schedule_preparation_session_use_case.dart), [기존 use-case tests](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/domain/use-cases/schedule_preparation_session_use_case_test.dart) | 실제 workflow와 repository boundary를 연결하는 기존 구조가 있다. 새 테스트용 session 구현을 만들지 않고 이 경계를 재사용한다. A07 구현 이후 API 변경은 해당 최종 소스로 갱신한다. | +| [A06 실제 periodic:138](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart#L138), [plugin 연결:166](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart#L166) | 이미 생산 Timer.periodic을 실행하고 NotificationService→MethodChannel show까지 도달한다. T04에서 동일 결함을 새로 발견/구현했다고 하지 않는다. | +| [A06 final validation](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/plans/audit-2026-09-23/a06-final-validation.json) | 구현 `d06474d4c645794644075173f76afb9ff4445047`의 신규 경계 47개, 전체 929개, analyzer 및 관련 정책 결과가 저장되어 있다. 이는 그 소스의 증거이며 A07 WIP나 이후 T04 구현의 통과 결과가 아니다. | +| [D03 startup widget 회귀](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/startup/local_reset_recovery_test.dart), [A12 notification launch widget](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/test/presentation/shared/router/notification_launch_widget_test.dart) | pre-DI 복구/실제 router/tap에 기존 검증이 있다. 그러나 stub Auth를 사용하는 router 테스트가 실제 AuthBloc의 load/stream 연결을 증명하지는 않는다. 기존 범위를 유지하고 누락된 연결만 보완한다. | +| [CONTEXT](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/CONTEXT.md), [Recovery ADR0023](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/adr/0023-preserve-local-data-when-migration-fails.md), [이력 ADR0034](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/adr/0034-retain-schedule-history-until-user-deletion.md) | Local Profile은 서버 인증이 아니다. Recovery는 데이터·키를 보존하고 정상 제품/알림 처리를 제한한다. 이력은 사용자 삭제까지 보존하며 명시 삭제 후 원문을 숨겨 보관하지 않는다. | + +## 확정한 테스트 계약 + +### 1. 실제 진입점과 직접 이벤트 단위 검증의 구별 + +- Timer 회귀는 clock을 전진시켜 제품이 생성한 periodic Timer가 실제 refresh를 발생시키도록 한다. 직접 ScheduleTick/Refresh를 add한 unit은 유효할 수 있으나 실제 scheduler 연결 증거로 세지 않는다. +- 화면 연결은 실제 tap/접근성 액션, 앱 lifecycle 전달, 실제 stream emission을 사용한다. callback을 직접 호출하거나 최종 state를 stub하는 검증은 그 제한을 기록한다. +- A06의 신선도·연속 paused·최대 한 번 시도·cold restore/resume 무재생·privacy·권한 및 generation 경계를 재사용한다. 테스트 개수 증가를 위해 동일 행렬을 복제하지 않는다. +- 성공 판정은 필요한 실제 결과로 한다. UI만 사라진 것과 DB에서 삭제된 것, plugin 요청 수락과 OS 표시, 가짜 저장소 값 변경과 durable 저장소 재열기 결과를 구별한다. + +### 2. Local Profile 준비 상태의 실제 흐름 + +Auth라는 기존 코드 명칭은 서버 로그인 계약을 뜻하지 않는다. `Local Profile`, `Recovery Mode`, `Local Data Reset` 등 기존 용어를 그대로 사용한다. 광범위 rename은 C06에 남긴다. + +1. 초기 loading에서 실제 load 성공 후 privacy migration 성공, 그다음 user stream 연결 순서를 확인한다. 어느 단계의 실패인지 독립 제어하고 뒤 단계가 잘못 시작되지 않는지 관찰한다. +2. 최초 정상 로드에서 유효한 empty/incomplete는 onboarding, ready profile은 정상 화면으로 전이한다. 이전에 ready였는데 예상치 않게 row가 없어진 경우에는 명시 reset/restore lifecycle과 구별한다. 누락을 새 설치로 오인하여 임의 profile을 만들거나 onboarding으로 덮지 않고 설명 가능한 복구 상태를 제공한다. +3. load/privacy/user stream 실패는 기존 데이터와 키를 보존한 Recovery Mode다. 정상 Schedule 구독 및 새 예약을 위한 reconciliation을 시작하지 않는다. 이는 D01/D03/A11이 소유한 필요한 privacy cleanup이나 확정된 초기화 복구 자체를 금지하는 뜻이 아니다. +4. 동일 구독 요청, 같은 ready emission 반복으로 활성 subscription·timer·초기 reconciliation이 중복 생성되지 않는다. 실제 profile 변경은 최신 UI에 반영하고, 필요한 후속 reconciliation은 기존 A15 coalescing/owner를 따른다. 모든 emission당 영구적으로 요청 1회 같은 과도한 기대를 고정하지 않는다. +5. retry는 이전 stream 구독 취소의 완료와 세대 검사를 거친 새 시도다. load/privacy/stream/zero-delay 등 await 뒤 currentness를 확인한다. close, 더 최신 emission, reset/restore generation 변경 후 이전 작업은 새 runtime에 후속 구독·예약·화면 전이를 적용하지 않는다. +6. 현재 구현에 없는 stream recovery나 currentness, 중복 구독 방어는 D01/C06의 제품 변경으로 연결하고 회귀와 함께 수정한다. 현재 결함을 테스트 기대값으로 합리화하지 않는다. + +### 3. 완료 이력 삭제의 잘못된 기대값 교체 + +정책은 [U01 #611](https://github.com/DevKor-github/OnTime-front/issues/611)이 소유한다. 완료/지각/비정상 및 과거 미완료 이력은 명시 확인 후 단일 삭제하며, 반복 이력은 이 회차만 삭제한다. 실제 유효한 준비 중 run/early session은 종료 안내와 함께 삭제를 차단한다. 완료 이력 편집을 함께 허용하지 않는다. + +- 현재 ‘완료 삭제 숨김’/‘진행 중 삭제 가능’ 기대값은 U01 제품 변경과 같은 작업에서 수정한다. 제품이 아직 미구현인 상태에서 기대값만 바꾸거나 fake에 미래 행동을 만들어 pass시키지 않는다. +- 실제 삭제 액션→확인/취소→제품 workflow→transaction 결과→목록/상세 갱신을 검증한다. 반환값이나 버튼 수만 확인하지 않는다. +- 선택 원문·전용 자원의 삭제, 공유 자원의 보존, 익명 점수 집계의 불변, 반복 규칙/다른 회차 보존 및 exclusion은 실제 저장소 read-back과 새 instance에서 확인한다. +- confirmation 후 stale/generation 충돌, commit 전 실패 보존, commit 후 OS 정리 pending, 현재 세대 명시 재시도의 동작은 U01/A15/D03/T03 기준을 사용한다. 원문 삭제와 OS 정리 완료를 하나의 성공으로 섞지 않는다. +- U01의 KO/EN·200% 글자·접근 가능한 삭제 액션 및 실제 기기 수용 기준은 별도로 남긴다. 호스트 widget 테스트로 그 전부가 완료되었다고 하지 않는다. + +### 4. 복제 fake와 oracle 정리 + +실제 `SchedulePreparationSessionUseCase`를 사용하고 repository/clock/channel 등 I/O 경계에서만 입력, fault, 타이밍을 제어한다. read/write spy나 명시 반환 fixture는 유지할 수 있다. 도메인 시간 파생·replay·prompt 유효성·부분 결과 판정을 fake 안에 다시 쓰지 않는다. 필요하면 작고 이름이 분명한 fixture builder를 공유하되 다른 test 파일의 실행부나 알고리즘에 의존하는 제2 제품 구현을 만들지 않는다. + +기대 결과는 생산 derive 함수를 다시 호출해 얻지 않는다. 다음과 같은 작은 고정 UTC 산술 표를 기준으로 한다. + +| 입력/관찰 시각 | 독립 기대 | +|---|---| +| 세 단계 60/120/60초, 명시 run 시작 `t0`, `t0 + 90초` | 첫 단계 완료, 두 번째 단계 경과 30초, 세 번째 0초. | +| `t0 + 90초`에 사용자가 실제 skip action 수행 | 현재 두 번째 단계를 건너뛰어 세 번째 경과 0초로 이동. | +| 그 run의 `t0 + 105초` | 세 번째 경과 15초. 해당 skip을 시간 경과로 두 번 적용하지 않음. | +| 같은 action 목록을 저장한 뒤 새 session/use case instance로 복원 | 같은 관찰 시각에 동일한 단계/경과. run identity·marker 보존/현재성은 별도 관찰. | + +고정 UTC와 명시 run 기준으로 시간대 불확실성을 분리한다. 이 표를 일반 replay oracle 엔진으로 확장하지 않는다. invalid/confirmation-required 상태는 생산 workflow가 보존해야 하는 marker를 실제로 기록하고, 새 instance가 그것을 읽어 명시 시작 전 중단된 상태를 유지하는지 검증한다. fake가 지운 marker를 테스트가 다시 넣는 보정을 제거한다. T03의 실제 저장소/암호문/프로세스 실험과 중복되는 경우 같은 artifact를 연결한다. + +## 검증 행렬 + +아래는 T04에서 유지·추가해야 할 수용 시나리오다. `기존 재사용`은 해당 소스의 증거가 있다는 의미이고, `추가/의존`은 아직 실행·통과했다고 주장하지 않는 항목이다. 정확한 테스트 함수/최종 SHA/결과/산출물을 구현 시 각 행에 연결한다. + +| ID | 진입점·상황 | 기대 결과 / 증거 | 상태·책임 | +|---|---|---|---| +| E01 | 실제 periodic→paused 자동 인접 전환→plugin | 일반 문구·최소 payload, 최대 한 번 시도. timer를 직접 이벤트로 대체하지 않음. | A06 기존 재사용. | +| E02 | periodic 지연/resume/cold restore/skip/clock 차이 | 현재 진행만 맞추고 지난 알림 재생 없음. | A06 기존 재사용. | +| E03 | permission/setup await 중 session/generation/lifecycle 변경 | 오래된 show 요청 0, 이미 수락한 OS 동작은 공통 owner 정리 경계로 처리. | A06/A15 기존 재사용. | +| E04 | 실제 App 생성의 Auth 요청 | load→privacy→stream이 연결되고 실제 router에 준비 상태 반영. | 추가, D01. | +| E05 | initial valid empty/incomplete stream | 정상 onboarding, 정상 schedule 구독/새 예약 reconciliation 0. | 추가, D01. | +| E06 | ready stream | 정상 화면과 활성 Schedule 구독, 필요한 초기 reconcile. | 추가. | +| E07 | load 실패 | 보존 Recovery, privacy/stream/정상 구독의 잘못된 후속 실행 없음; 허용된 cleanup은 구별. | 추가, D01/A11. | +| E08 | privacy 실패 | 보존 Recovery, stream/정상 제품 작업 미시작, 민감 오류 원문 비표시. | 추가, D01/A11. | +| E09 | ready 전/후 user stream 오류 | 보존 Recovery, 정상 화면/새 예약 처리 제한, 미관찰 오류 없음. | 추가, D01/C06 제품 수정 필요. | +| E10 | 이전 ready 후 설명되지 않는 row 소실 | 명시 reset/restore와 구별, 임의 새 profile/자동 삭제/거짓 onboarding 없음. | 추가, D01. | +| E11 | 구독 이벤트 연속 중복 | 활성 구독 및 timer 하나, 이전 구독 취소 완료/명시 소유권 관찰. | 추가, D01/C06. | +| E12 | 같은 ready emission 반복/변경된 profile emission | 초기 효과 중복 없음; 실제 변경된 최신 UI는 반영; A15 요청 병합 의미 보존. | 추가. | +| E13 | failed load→실제 retry action→성공 | 새 시도와 유효 구독, 데이터/키 유지, retry가 이전 handler를 병렬 잔존시키지 않음. | 추가, D01/T03 F22. | +| E14 | load/privacy/stream/zero-delay 중 close | 뒤늦은 state/구독/reconcile 없음, boundary 반환 후 자원 정리. | 추가. | +| E15 | ready A의 await 중 newer emission B | A의 후속 효과가 B 상태를 덮거나 이전 준비 상태로 예약하지 않음. | 추가. | +| E16 | reset/restore generation 교체 중 과거 Auth 결과 | 이전 결과가 새 runtime/profile/알림 owner를 변경하지 않음. | 추가, D01/A15/D03. | +| E17 | U01 정상/지각/비정상 완료 이력 삭제 액션→확인 | 완료 편집은 계속 제한, 선택 원문 삭제/목록 반영/aggregate 보존. | U01 구현 후 통합. | +| E18 | U01 과거 미완료 및 반복 완료 회차 삭제 | 반복은 이 회차만, exclusion 기록, 규칙·다른 이력/미래 회차 유지. | U01 구현 후 통합. | +| E19 | 실제 유효 준비 중/early run 삭제 시도 | 삭제 실행 0, 먼저 종료 안내/준비 화면 연결. stale 상태는 임의 삭제하지 않음. | U01 구현 후 통합. | +| E20 | 삭제 확인 취소 및 연타 | 취소 무변경, 확인 연타 single-flight, 중복 revision/집계 영향 없음. | U01 구현 후 통합. | +| E21 | 삭제 확인 뒤 변경/시작/복원/같은 ID 재사용 | typed stale/conflict, 재조회/재확인, 다른 대상 삭제 없음. | U01/T03 기존 계약 연결. | +| E22 | 삭제 transaction 실패 / commit 후 OS 정리 실패 | 전자는 원본 유지, 후자는 삭제 유지+pending·현재 세대 재시도. DB 허위 rollback 없음. | U01/A15/D03/T03 연결. | +| E23 | 삭제 후 새 저장소 instance / 새 백업 | 원문·전용 자원 없음, 공유 자원 유지, 익명 aggregate 유지. | U01 실제 저장소 증거 연결. | +| E24 | 실제 session의 고정 60/120/60초 진행 | t0+90의 두 번째 30초, 실제 skip 뒤 t0+105의 세 번째 15초. | 추가, C04/A07와 겹치는 증거 재사용. | +| E25 | 위 action/run 저장 후 새 session instance 복원 | 독립 기대와 동일, fake replay·생산 derive 호출 expected 없음. | 추가. | +| E26 | invalid 또는 confirmation-required snapshot | 실제 marker 보존, 새 instance도 명시 시작 전 중단; 손수 재삽입 보정 없음. | 추가, A07 API 확정 후. | +| E27 | 실제 명시 start/skip/finish action | 실제 use case의 receipt/저장 상태를 UI에 반영, 경계 실패/부분 결과는 T03와 연결. | A07 확정 증거 재사용 후 gap 보완. | +| E28 | prompt ready/rejected/unavailable | 실제 repository 조회와 session 판정, 삭제/완료/실패에 맞는 결과; fake 판정 복제 없음. | A12/A07 회귀 재사용 후 gap 보완. | +| E29 | 명시 테스트 종료·retry·새 instance 교체 | boundary가 반환된 후 subscription/timer/error/DI/platform mock 누수 없음. | 모든 새 harness. | +| E30 | Android/iOS 실제 실행 | build/OS/기기/행동·관측 결과 분리 기록. plugin 수락≠표시·background 보장, 미실행은 device pending. | A06/U01/D01/T02 관련 조건 유지. | + +## 구현 순서와 의존성 + +1. 현재 고정 baseline의 테스트 목적·entry point·oracle·boundary·증거를 매핑한다. A06/D03/A12/T03 existing rows를 연결해 이미 검증한 경로와 남은 gap을 구별한다. +2. A07 구현이 확정된 후 실제 session use case API와 receipt를 사용하도록 preparation 연결 테스트를 전환한다. C04 도메인 계산기 정리가 나중이어도 실제 현행 use case 호출은 가능하며, T04만을 위한 두 번째 계산기를 만들지 않는다. +3. D01/C06과 함께 Auth load/privacy/stream 오류·재시도·현재성·구독 수명 제품 결함을 수정하고 실제 App/router 연결 회귀를 추가한다. 기존 pre-DI D03 shell 검증과 역할을 구별한다. +4. U01 구현과 함께 삭제 숨김/진행 중 허용 기대값을 수정하고 실제 UI→workflow→저장소 검증을 연결한다. T03는 장애 주입·commit 전후 보존/부분 결과의 공통 증거를 담당한다. +5. 영향 범위 unit/widget, analyzer, 정책 검사 및 전체 회귀를 해당 최종 소스에서 실행한다. 원래 결함을 재현한 실패와 수정 이후 통과를 연결하고, CI는 exact head SHA와 artifact read-back으로 기록한다. +6. 플랫폼이 필요한 수용 기준은 원래 이슈/T02의 기기 증거를 연결한다. 미실행 의존 행을 skip하여 성공률만 높이거나 T04를 완료로 닫지 않는다. + +관련 이슈: [A06 #598](https://github.com/DevKor-github/OnTime-front/issues/598), [A07 #599](https://github.com/DevKor-github/OnTime-front/issues/599), [D01 #603](https://github.com/DevKor-github/OnTime-front/issues/603), [U01 #611](https://github.com/DevKor-github/OnTime-front/issues/611), [T03 #613](https://github.com/DevKor-github/OnTime-front/issues/613), [A12 #591](https://github.com/DevKor-github/OnTime-front/issues/591), [A15 #594](https://github.com/DevKor-github/OnTime-front/issues/594), [D03 #595](https://github.com/DevKor-github/OnTime-front/issues/595). C04/C06/T01/T02는 감사의 해당 항목과 연결하며 아직 번호를 임의로 만들지 않는다. + +## 검증 방식과 수용 기준 + +- [ ] 각 핵심 회귀에 제품 계약, 실제 진입점, 독립 기대 결과, 제품 workflow, 교체한 I/O 경계를 명시한다. +- [ ] 기존 A06의 실패 재현·47개 신규 경계·929개 전체 결과를 정확한 소스 증거로 재사용하고 최신 WIP 결과로 확대하지 않는다. +- [ ] AuthUserSubscriptionRequested→load/privacy/stream→실제 router/후속 구독 경로가 성공·오류·retry·중복·close·generation 교체에서 확인된다. +- [ ] U01 제품 구현과 같은 변경에서 잘못된 삭제 기대가 교체되고 실제 저장소 결과까지 검증된다. 제품 미구현을 fake-only pass로 가리지 않는다. +- [ ] preparation replay/prompt 판정을 복제한 fake 및 marker 재삽입 보정이 실제 use case 호출과 independent oracle로 교체된다. 단순 fixture/helper 테스트를 의미 없이 삭제하지 않는다. +- [ ] 신규 핵심 회귀의 원 결함 실패 원인과 수정 후 SHA/명령/결과를 기록한다. 이미 A06가 보유한 동일 negative 실험은 링크로 재사용한다. 전면 mutation 도구 도입을 의무화하지 않는다. +- [ ] fake clock/latch/명시 상태 대기와 bounded watchdog으로 경계를 제어한다. 무작위 sleep, 통과할 때까지 rerun, 광범위 pumpAndSettle로 실패를 숨기지 않는다. +- [ ] teardown에서 test DI/platform handler/stream/timer가 다음 테스트로 누출되지 않는다. 실제 I/O가 설계상 owner를 유지하는 동안 무리하게 작업을 취소하여 pending=0을 만들지 않고, barrier 반환 후 정상 정리를 검증한다. +- [ ] 실제 storage read-back, in-memory boundary, host plugin/channel, host process, Android/iOS 관측을 다른 evidence level로 기록한다. T03의 저장소/프로세스 증거는 재사용하고 같은 harness를 복제하지 않는다. +- [ ] tests/analyze/CI의 실행 소스 SHA, 명령, test 이름, fixture provenance, artifact hash 및 실제 결과를 남긴다. 비공개 사용자 데이터나 실사용 설치를 fixture로 사용하지 않는다. +- [ ] 원래 제품 의존성·기기 조건을 충족하지 못한 행은 명시적으로 남긴다. 단순 coverage 또는 수량 증가, stub 결과, 아직 실행하지 않은 시나리오로 완료 선언하지 않는다. + +새 도메인 용어와 ADR는 추가하지 않는다. 기존 CONTEXT의 Local Profile·Recovery Mode·Schedule History·Preparation Run/Action Event를 사용하며, 테스트 연결과 oracle 정책은 이 이슈에 기록한다. 이번 전담 에이전트는 공유 CONTEXT/ADR를 수정하지 않았다. + +## grill-with-docs 실제 전체 문답 + +전담 서브에이전트 `/root/grill_t04`가 `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`와 코드·기존 문서를 읽고 root에게 한 번에 하나씩 질문했다. root가 사용자에게 위임받은 결정자로 답했다. 아래는 실제 질문·답변의 전체 원문이며, 권고와 최종 보충 조건을 모두 위 계약에 반영했다. + +### Q1 — 기존 증거 재사용과 T04의 소유 범위 + +**서브에이전트 질문** + +> T04 Q1 — grill-with-docs를 읽고 고정 HEAD e6c1438e를 확인했습니다. 실제 남은 결함은 auth_bloc_test가 AuthState 두 개만 생성하고, schedule_detail_test가 ‘진행 중 삭제 가능/완료 삭제 숨김’을 기대하며, FakeSchedulePreparationSessionUseCase가 복원·이벤트 replay·prompt 판정을 복제한다는 점입니다. T04 범위를 A06 실제 periodic→plugin 회귀는 기존 증거 그대로 재사용하고, AuthUserSubscriptionRequested→load/privacy/stream→상태·후속 구독/알림 요청의 연결 검증, U01 실제 삭제 UI→workflow→저장소 검증, 복제 fake 제거와 빠진 실제 workflow 회귀로 확정할까요? 권고: 제품 행동 변경은 D01/U01/A07/C04 원래 이슈에 귀속하고 T04는 잘못된 oracle·연결 누락을 소유합니다. AuthState 단위 테스트와 단순 boundary fake는 유효하면 유지하고, fake 삭제 수/coverage 숫자로 완료를 판정하지 않겠습니다. + +**root 답변** + +> Q1채택. A06실제periodic→plugin증거재사용, Auth요청→실제load/privacy/stream연결·후속subscriptions/reconcile 현재성증명, U01delete UI→실제workflow/storage는해당구현완료후통합회귀. 현재잘못된expectation만미리새행동pass로바꾸지말고원래제품issue수정과같이수정. 단순상태/equality/helper테스트유효하면유지, mock은경계I/O와fault/timing제어, 도메인replay/판정알고리즘복사는실제usecase호출로교체. 필요하면작고이름명확한fixturebuilder를공유하되제2생산구현만들지않음. 제품결함소유원래이슈연결,T04는oracle및연결증거,삭제개수/coverage목표없음. + +### Q2 — 실제 Local Profile 준비 전이와 비동기 현재성 + +**서브에이전트 질문** + +> T04 Q2 — Auth 연결의 현재 코드에서는 stream onError가 addError만 하고, onData의 await Future.delayed(0) 뒤 state.status만 검사하므로 이전 ready emission의 후속 작업과 재구독/close가 겹칠 수 있습니다. 기대 정책을 확정할까요? 권고: 여기서 Auth는 서버 인증이 아닌 Local Profile 준비 상태입니다. load 또는 privacy migration 실패 및 실행 중 user stream 오류는 데이터/키를 보존한 Recovery Mode, 정상 Schedule 구독·새 reconcile 요청 0; load 성공→privacy 성공→stream 연결 순서를 관찰합니다. empty/incomplete는 onboarding, ready만 정상 진입합니다. 동일 구독 요청/동일 ready emission 반복은 중복 활성 subscription/timer/초기 reconciliation을 만들지 않고, 최신 profile 갱신은 UI에 반영하되 기존 A15 coalescing을 우회하지 않습니다. retry는 이전 구독을 정리한 새 시도이고, load/privacy/stream/zero-delay 대기 중 close·reset/restore generation 변경·더 최신 emission이 오면 이전 후속 side effect가 새 runtime에 실행되지 않습니다. stream 오류→복구·중복구독·generation 방어가 현재 누락이면 D01/C06에 명시 연결하여 제품 수정과 회귀를 함께 수행하고, T04에서 현재 동작을 정답으로 고정하지 않겠습니다. + +**root 답변과 보충 조건** + +> Q2채택. Auth=Local Profile준비상태,serverlogin아님. initial validempty/incomplete만onboarding,이전ready인데예기치않게row없음은기존gate/reset의명시lifecycle인지구별하며임의신규profile로치환금지(설명가능한복구상태). load/privacy/streamfailure는보존Recovery,정상schedule구독/새예약reconcile없음. 다만D01/D03/A11이소유하는필요한privacycleanup/초기화복구자체를막는뜻아님. readyrepeat는활성subscription/timer/초기reconcile중복생성없고실제변경은최신UI에반영. retry이전구독cancelawait/세대검사,모든await후currentness,close및newemission/generation구형sideeffect폐기. 기존누락은D01/C06연결제품수정+회귀동시,실제bridge연결을tests로확인하고domain행동복제fake에서만검사하지않음. + +### Q3 — 독립 산술 oracle·marker 보존·증거와 종결 기준 + +**서브에이전트 질문** + +> T04 Q3 — 검증 oracle와 종결 기준을 확정하겠습니다. 고정 HEAD의 schedule_bloc_test에는 ‘fake는 invalid state를 지우지만 production은 safe marker를 보존’이라며 테스트가 직접 marker를 재삽입하는 구간까지 있어 실제 재시작 경계를 가립니다. 권고: 실제 SchedulePreparationSessionUseCase와 얇은 repository/clock/channel 경계를 연결하고, 60/120/60초 준비의 t0+90=2단계30초, 그 시점 실제 skip action→t0+105=3단계15초처럼 작고 독립 산술 가능한 입력/기대표를 사용합니다. expected를 같은 생산 derive 함수로 생성하거나 fake에 replay를 복제하지 않습니다. invalid/confirmation marker 보존·명시 시작·복원은 실제 workflow와 새 instance의 repository read-back으로 확인하고, UI 연결 테스트는 실제 tap/stream/lifecycle/periodic 진입을 거칩니다. 단순 unit의 public event 직접 주입은 유지하되 ‘실제 timer/UI 경로’ 증거로 세지 않습니다. 각 신규 핵심 회귀는 원 결함에서 실패한 이유/수정 후 성공과 정확 SHA를 남기고, 이미 A06가 보유한 negative 실험을 반복할 필요는 없습니다. sleep/광범위 pumpAndSettle 대신 fake clock/latch/명시 상태·bounded watchdog, teardown 후 pending timer/subscription/error 0을 확인합니다. host channel 수락과 Android/iOS 표시·재시작 증거는 구분하고 기존 A06/U01/D01 device pending은 그대로 두며, 미구현 의존성은 skip-pass가 아니라 blocked 항목으로 남깁니다. 이 계약으로 확정할까요? + +**root 답변과 보충 조건** + +> Q3채택. 60/120/60초,t0+90=step2 30초,그시점skip→t0+105=step3 15초 독립산술oracle사용(고정UTC/명시run기준으로timezone불확실성과분리). expected를생산derive호출로생성금지. fake가삭제한confirmationmarker를test가손으로복구하는보정제거,실제usecase/저장소새instance로보존검증. 실제tap/stream/lifecycle/timer와직접eventunit증거구분. 핵심회귀negative원인/afterSHA명시하되이미A06동일negative재실행안함. fakeclock/latch/명시상태watchdog/teardown정리채택,실제I/O fixture에서지속timer가설계상owner유지중이면강제0을위해제품작업취소하지말고barrier반환뒤정리검증. channel수락≠OS표시,기기조건/blockeddeps명시. 실제3문답+보충전체·근거·수용행렬동결해주세요. 새용어/ADR추가없음. + +## 이번 문서 작성의 완료 범위 + +고정 소스와 기존 검증 자료를 읽고 실제 3문답을 마쳤으며 이슈 본문만 작성했다. 새로운 테스트 실행·구현·GitHub 게시·커밋은 수행하지 않았다. root가 본문을 게시하고 위 의존성과 우선순위에 따라 구현·검증을 진행한다. A06의 기존 성공과 향후 T04/U01/D01 구현 성공을 혼동하지 않는다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index f3b2b78a..40b66d7c 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -394,7 +394,7 @@ "`lib/presentation/app/bloc/schedule/schedule_bloc.dart:379,387-409,508-514`; `test/presentation/app/bloc/schedule/schedule_bloc_test.dart:1236-1243`", "실제 timer/clock을 전진시켜 단계 변경 알림 1회 검증. resume catch-up 시 중복/불필요 알림 정책도 함께 검증." ], - "status": "code_verified_ci_device_pending", + "status": "code_verified_device_pending", "agent": "/root/grill_a06", "issue_number": 598, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/598", @@ -404,7 +404,8 @@ "verification": [ "Negative actual periodic regression reproduced before fix", "Final47 boundary and full929 pass; coverage88.74% (13092/14753); analyze/generated/local-only/Python11/manifest pass", - "Seven committed source blobs match validated freeze" + "Seven committed source blobs match validated freeze", + "Remote e6c1438e Flutter run35945420027: 929 tests/analyze, 88.79% (13099/14753); Android run35945419975 APK/manifest passed, artifact10786697671 identity read back" ], "blockers": [ "Remote CI pending", @@ -413,7 +414,8 @@ "sequence": 12, "readback_verified": true, "body_sha256": "b96545e7bb2a44582be5a68be08798a8c782648d5498236d198335004f1160ec", - "validation_file": "plans/audit-2026-09-23/a06-final-validation.json" + "validation_file": "plans/audit-2026-09-23/a06-final-validation.json", + "issue_body_sha256": "9965d0c12791a842a4f5565c717efd87b843e1bb307a10004db452ddfbfc4450" }, { "audit_id": "A07", @@ -732,16 +734,18 @@ "실패·복원·재시도 테스트를 우선한다.", "A04/A07/A08/A09와 D01-D05의 장애 주입, write 실패 후 재시도, concurrent operations, 중간 프로세스 종료, 복원 rollback. crypto tamper 검사는 header/frame 순서·잘림·final marker·다중 chunk도 포함." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_t03", + "issue_number": 613, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/613", "decisions_file": "plans/audit-2026-09-23/issues/T03.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 26 + "sequence": 26, + "readback_verified": true, + "issue_body_sha256": "ef7daa335c6ce7c312c619b043f901b02e30fc012c61ea4469ec2b22cb537d7a" }, { "audit_id": "T04", @@ -753,16 +757,18 @@ "실제 이벤트 경로를 테스트하고 잘못된 동작을 고정한 기대값을 수정한다.", "timer tick 직접 주입 대신 실제 scheduler; 상태만 검사하는 AuthBloc 테스트 보완; 완료 이력 삭제 숨김 기대 수정. mock 안에 생산 알고리즘을 복제하지 않음." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_t04", + "issue_number": 614, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/614", "decisions_file": "plans/audit-2026-09-23/issues/T04.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 27 + "sequence": 27, + "readback_verified": true, + "issue_body_sha256": "c7a41147aa612595c4bf049d47ac63f1a525b7ecadfde52ce7de3796a32aad84" }, { "audit_id": "T01", From c0907712429f5aa7a2ffbaf50332e665409dff7a Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:22:10 +0900 Subject: [PATCH 41/59] docs: define mobile E2E audit acceptance and workflow handoff --- plans/audit-2026-09-23/README.md | 9 +- .../c01-implementation-preflight.md | 13 ++ plans/audit-2026-09-23/issues/T01.md | 148 ++++++++++++++++++ plans/audit-2026-09-23/state.json | 12 +- 4 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 plans/audit-2026-09-23/c01-implementation-preflight.md create mode 100644 plans/audit-2026-09-23/issues/T01.md diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 906d7029..cb02d4c2 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -38,20 +38,15 @@ | D05 | [#606](https://github.com/DevKor-github/OnTime-front/issues/606) | 실제4문답·bounded stream/자료 한도·시간대/그래프 검증 기준 확정 | | U08 | [#597](https://github.com/DevKor-github/OnTime-front/issues/597) | 홈 overflow 선행 수정18 tests/analyze 통과, 전체 매트릭스·기기 검증 미완료 | | A10 | [#608](https://github.com/DevKor-github/OnTime-front/issues/608) | 실제4문답·civil/instant·오늘/달력·DST·명시 zone 편집·tzdb 변경 확인 계약,32개 검증 시나리오 확정 | - | U02 | [#609](https://github.com/DevKor-github/OnTime-front/issues/609) | 실제3문답·오프라인 zone 선택/기기 환산·DST/변경 재검토,23개 검증 시나리오 확정 | - | C08 | [#610](https://github.com/DevKor-github/OnTime-front/issues/610) | 실제3문답·현재 instant/세션보존/수명주기 갱신·오류/제한 구분,32개 검증 시나리오 확정 | - | U01 | [#611](https://github.com/DevKor-github/OnTime-front/issues/611) | 실제3문답·이력 단일삭제/공유원문·점수보존/취소ownership,32개 검증 시나리오 확정 | - | U03 | [#612](https://github.com/DevKor-github/OnTime-front/issues/612) | 실제3문답·최신 토글의도/저장-OS반영 구분/화면이탈·세대 경계,28개 검증 시나리오 확정 | - | T03 | [#613](https://github.com/DevKor-github/OnTime-front/issues/613) | 실제3문답·실패경계22/암호문12행렬·기존증거 재사용/실제저장소·새프로세스 검증 계약 | - | T04 | [#614](https://github.com/DevKor-github/OnTime-front/issues/614) | 실제3문답·실제 진입점/독립 oracle·Auth 복구/삭제/세션 연결30개 검증 계약 | +| T01 | [#615](https://github.com/DevKor-github/OnTime-front/issues/615) | 실제3문답·정상main/암호화저장소·OS수신/프로세스재실행·양플랫폼18개 E2E 행렬 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **28/64개**를 게시하고 원격 본문 동일성을 확인했으며 **36개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07 실제 DB 시작 결함3개를 재현해 다음 구현 중이다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **29/64개**를 게시하고 원격 본문 동일성을 확인했으며 **35개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07 실제 DB 시작 결함3개를 재현해 다음 구현 중이다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/c01-implementation-preflight.md b/plans/audit-2026-09-23/c01-implementation-preflight.md new file mode 100644 index 00000000..e264756c --- /dev/null +++ b/plans/audit-2026-09-23/c01-implementation-preflight.md @@ -0,0 +1,13 @@ +# C01 최신 구현 준비 메모 + +2026-09-24 root read-only 조사. 제품 기준 e6c1438e/문서7a6825f7, A07 WIP는 전담 agent 소유이며 본 메모는 구현 완료 근거가 아니다. 상세 계약과 실제 문답은 issues/C01.md 및 #600이 권위다. + +- 최신 MyDataScreen 경로는 lib/presentation/my_page/my_data_screen.dart이다. concrete BackupService/LocalDataResetService/ReconcileAlarmsUseCase를 계속 직접 호출한다. `_loadFreshness` 예외 상태가 없고 원문 오류 문구·한국어 고정 문구가 남는다. +- D03가 reset 단계를 이미 구현했다. core/database/local_reset_protocol.dart의 LocalResetResult와 LocalDataResetService/journal을 재사용한다. LocalResetProgressScreen(operation, initialResult)은 typed receipt를 표시하며 timeout은 owner를 취소하지 않는다. 별도 삭제 coordinator를 만들지 않는다. +- LocalDataRecoveryScreen도 D03에서 typed reset receipt/partial progress로 바뀌었다. C01 baseline의 예외 처리 부재를 현재 결함으로 그대로 반복하지 않고 최신 코드에서 UI 의존성 분리 gap만 확인한다. +- ScheduleMutationAlarmEffectsCoordinator는 durable commit 뒤 synchronous reconcile 접수를 보장하지만 Future로 후속 receipt를 전달하지 않는다. A15 접수/후속 drain과 실제 OS owner는 유지하고 C01 결과에 commit/delivery 상태를 잃지 않게 연결한다. +- LocalDataOperationGate는 run 진입 시 replacesData이면 generation을 먼저 올린다. C01 preview token 재검사에서 자기 generation 증가를 stale로 간주하지 않는 claim 경계가 필요하며 실제 staging/replacement는 A09와 연결한다. +- C01은 실제 화면→workflow 적용을 구현하되 A08 aggregate transaction/A09 staging-runtime/D02 손상 DB restore 통합 AC를 미구현 상태에서 완료로 닫지 않는다. 현재 메모리 BackupCandidate를 verified staging이라고 이름만 바꾸지 않는다. +- A07 API/실제 검증이 동결되면 최신 결과 및 알림 owner를 다시 읽고 구현한다. source/codegen/Flutter 실행은 A07 전담이 끝난 뒤 순서대로 진행한다. + +원격 PR 본문 갱신은 자동 승인 검토가 2026-09-24 명시 payload/destination 승인이 부족하다는 이유로 거절하여 사용자 응답을 기다린다. 준비 본문은 /tmp/ontime-audit-pr.md, 대상은 DevKor-github/OnTime-front Draft PR #585이다. 이 제한은 거절된 PR 본문 갱신에만 적용하며 다른 승인된 구현·이슈 업무를 중단하지 않는다. 우회/간접 게시 금지. diff --git a/plans/audit-2026-09-23/issues/T01.md b/plans/audit-2026-09-23/issues/T01.md new file mode 100644 index 00000000..11207493 --- /dev/null +++ b/plans/audit-2026-09-23/issues/T01.md @@ -0,0 +1,148 @@ +# [P1][T01] Android·iOS 실제 앱 E2E와 오프라인 새 설치·알림·진행·백업·복원·초기화 여정 검증 + +## 문제와 결과 목표 + +현재 테스트와 플랫폼 컴파일 검증은 상당 부분 존재하지만, 실제 `main`에서 시작해 startup/privacy boundary, DI, 설치별 암호화 저장소, 화면 입력, OS 알림 수신·탭, 준비 진행·완료, 프로세스 재실행까지 연결하는 재현 가능한 E2E suite는 없다. 여러 테스트의 개수나 성공을 더해도 이 연결이 검증되지는 않는다. + +T01은 Android·iOS에서 **실제 앱의 핵심 성공 여정을 재현하고, 자동 smoke와 물리 기기의 release candidate 검증을 구별한 실행 증거**를 만든다. 제품은 Android·iOS local-only이며 로그인·서버·원격 analytics·cloud test telemetry를 추가하지 않는다. Native UI 및 전체 권한/OS 조합은 T02, 장애·암호문·복원 실패는 T03, 실제 이벤트 연결과 독립 oracle은 T04를 재사용한다. + +- 우선순위: **P1**, 감사 항목 T01. 전담 grill: `/root/grill_t01`. +- 적용 skill: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +- 조사 고정 SHA: **`e6c1438ebcd641386081bb5aa5569e1b7da7ee15`**. 아래 소스 링크는 이 커밋을 기준으로 한다. +- 실제 문답 3개와 보충 조사로 결정했다. 이번 문서 작성에서는 제품·테스트 수정, Flutter 실행, 모바일 실행, GitHub 변경을 하지 않았다. 병행 중인 A07 WIP는 이 고정 소스의 실행 증거로 인용하지 않는다. +- **감사 baseline 정정:** 최초 감사의 “iOS RunnerTests는 testExample placeholder”는 현 기준에 맞지 않는다. A02가 malformed export 입력 검증으로 교체했다. 유효한 native contract 테스트는 유지하고, 여전히 비어 있는 실제 앱 E2E를 추가한다. + +## 고정 근거와 기존 증거의 재사용 한계 + +| 근거 | 확인한 내용과 처리 | +|---|---| +| [감사 T01–T04](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/App-Improvement-Audit-2026-09-23.md#L114) | T01의 핵심은 비행기 모드 새 설치→일정→실제 전달→진행·완료→재실행이다. RunnerTests 상태는 아래 최신 코드로 정정한다. | +| [pubspec.yaml](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/pubspec.yaml) / 고정 트리 `integration_test/` 부재 | Flutter unit/widget 의존성은 있지만 `integration_test` 의존성과 실제 앱 integration entrypoint가 없다. 폴더만 추가하거나 기존 widget 테스트를 이동하는 것이 목표가 아니다. | +| [main:17–49](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/main.dart#L17) | LocalStartupGate→bootstrapWithPrivacyCleanup→LocalDataLifecycle.bootstrap→configureDependencies→ResetAwareApp/App을 연결한다. App만 pump하고 startup/DI를 생략한 테스트는 이 경로의 E2E가 아니다. | +| [App:30](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/presentation/app/screens/app.dart#L30) / [실제 router](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/presentation/shared/router/go_router.dart) | Auth 요청, OS initial launch, lifecycle refresh, notification tap route가 실제 앱 생성에 연결된다. 최종 state를 주입하는 방식은 이 연결의 증거가 아니다. | +| [LocalDataLifecycle](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/core/database/local_data_lifecycle.dart) | secure storage cutover marker와 interrupted reset 복구가 DI 이전에 실행된다. 테스트 fixture가 marker를 미리 넣어 첫 실행을 우회하면 Offline Cold Start가 아니다. | +| [native DB:6–23](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/core/database/open_database_native.dart#L6), [InstallationKeyStore:8–48](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/lib/core/database/installation_key_store.dart#L8) | 실제 파일·설치 키·native SQLCipher 경로가 있다. in-memory SQLite 또는 fake secure store로 대체한 테스트를 암호화 모바일 저장소 실행이라 부르지 않는다. 실제 cipher 계약의 세부 검증은 D04/T02와 연결한다. | +| [RunnerTests:8](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/ios/RunnerTests/RunnerTests.swift#L8) | 잘못된 export argument에 한 번의 실패 응답을 검증하는 실제 테스트 소스다. placeholder는 아니며 앱 전체 UI·picker·재실행 검증도 아니다. | +| [모바일 백업 검증](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/Mobile-Backup-Verification.md) | Dart 회귀와 Kotlin/Swift typecheck가 기록되어 있고 실제 native picker·양방향 복원은 미실행으로 구분되어 있다. T01은 성공 여정을 연결하고 전체 provider/오류 행렬은 A02/T02/T03을 재사용한다. | +| [Flutter Testing](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/.github/workflows/flutter_test.yml#L14) | Ubuntu, Flutter 3.44.4, generator, local-only/generated policy, analyze, unit/widget coverage 80% gate가 있다. 같은 작업을 중복 runner로 다시 만들지 않는다. | +| [Android Notification Contract](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/.github/workflows/android-notification-contract.yml) | 일회용 검증 서명 release APK의 packaged/merged manifest 검사와 digest를 남긴다. `device_delivery_verified: false`이며 실제 OS 전달 또는 store candidate 증거가 아니다. | +| [A06 final validation](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/plans/audit-2026-09-23/a06-final-validation.json) | 해당 소스의 929 host tests 및 실제 periodic→plugin channel 연결 증거를 재사용한다. channel 수락은 물리 OS 수신이 아니고 이후 T01 구현의 실행 결과도 아니다. | +| [반복 일정 iOS QA](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/design/recurring-schedules/implementation/native-ios/README.md) | 이전 소스의 debug simulator 생성·재실행·전용 준비 독립성·등록부 관측이 있다. 호스트 네트워크를 바꾸지 않았고 비행기 모드·물리 기기·실제 전달은 검증하지 않았다고 명시한다. 새로운 전체 E2E의 통과 근거로 재표시하지 않는다. 해당 simulator와 데이터는 보존한다. | +| [CONTEXT의 Offline Cold Start](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/CONTEXT.md#L108), [ADR0024](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/adr/0024-enforce-no-network-in-product-builds.md) | clean install·airplane mode·core flows가 제품 계약이다. 연결된 상태로 리소스를 내려받은 뒤 오프라인으로 바꾸는 warm cache 검증과 다르다. | + +[ADR0016](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/adr/0016-support-local-only-on-android-and-ios.md), [ADR0018](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/adr/0018-encrypt-active-data-with-a-device-bound-key.md), [ADR0020](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/adr/0020-reset-all-installation-owned-data.md), [ADR0026](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/adr/0026-use-a-cross-platform-backup-format.md), [ADR0028](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/adr/0028-require-a-verified-restore-preview.md), [ADR0029](https://github.com/DevKor-github/OnTime-front/blob/e6c1438ebcd641386081bb5aa5569e1b7da7ee15/docs/adr/0029-derive-preparation-runs-from-action-events.md)의 기존 용어·제품 계약을 따른다. 테스트 runner·evidence level은 구현/검증 설명이므로 새 glossary나 ADR를 추가하지 않는다. + +## 확정한 실행 계약 + +### 실제 앱 경로와 독립 기대 결과 + +- 정상 `main`→startup gate/bootstrap→실제 DI→실제 OS secure storage와 암호화 파일 DB→화면이 기본이다. 테스트 때문에 제품 workflow, repository, DB, 알림 서비스를 fake로 바꾸고 성공을 보고하지 않는다. +- 필요한 자동화 dependency와 entrypoint, 안정적인 접근성/테스트 식별자를 최소 범위로 추가한다. 제품 전용 테스트 화면, DB 주입, 설치 키 출력, data export debug channel을 추가하지 않는다. +- fixture는 합성 이름·장소·준비 단계·명시 시간대로 실제 onboarding/생성/편집 폼에서 만든다. 기대값은 입력한 fixture에서 독립적으로 산출한다. 생산 derive 함수 또는 테스트 내 복제 알고리즘으로 expected를 만들지 않는다. +- clock/외부 경계 injection이 필요하면 어떤 증거를 제한하는지 결과에 명시한다. 실제 OS 수신 행은 wall clock과 native provider를 사용한다. 빠른 clock 테스트를 실제 OS 전달로 바꿔 부르지 않는다. +- 준비 진행은 실제 시작 액션과 timer/lifecycle 경로로 관측한다. A07의 최초 durable startedAt과 개별 Preparation Run start를 혼동하지 않는다. 재실행 후 runtime은 A11의 검증/재확인 계약을 따르며, durable 일정 보존을 자동 진행 복구로 확대하지 않는다. +- OS 권한창·native file picker·잠금 화면·알림 탭처럼 Flutter widget tree 밖인 단계는 명시적인 native driver 또는 수동 기기 절차로 연결한다. API callback 직접 주입, registry 존재, plugin 성공 receipt만으로 실제 수신·탭을 판정하지 않는다. + +### 격리·첫 실행·프로세스 재실행 + +- runner는 자기 소유의 새 disposable Android AVD/iOS simulator와 run ID를 만든 뒤 대상 ID·앱 ID를 기록하고 검증한다. 설치, permission 설정, 프로세스 종료, 네트워크 변경, 정리 명령은 그 대상에만 적용한다. +- 기존 QA simulator `C8BA159D-A97A-458B-A5FC-B04A8B6F2848`, 다른 작업의 simulator, 사용자 기기는 자동 선택/초기화/삭제하지 않는다. 기존 hardware는 향후 명시 지정된 QA 대상에서만 검증한다. +- 새 simulator/명시 초기화된 QA 환경의 clean-install 상태를 확인한다. iOS uninstall만으로 secure-store 키가 제거되었다고 가정하지 않는다. 제품 reset으로 사전 marker를 만든 설치도 첫 설치 cold-start와 구별한다. +- 프로세스 재실행은 외부 runner가 실제 PID를 종료하고 **동일 설치·동일 데이터**로 cold launch한 뒤 UI에서 profile/일정/완료 기록을 다시 읽는다. widget 재-pump, DI 재생성, hot restart를 process relaunch로 쓰지 않는다. +- Android Settings force-stop은 일반 프로세스 종료와 구별한다. 기기 OS 제한을 숨기거나 force-stop 중 수신을 T01에서 보장하지 않는다. 사용한 종료 방식과 PID 전후를 기록한다. +- 실패 로그·스크린샷은 합성 데이터만 보존하고 재실행으로 원 실패를 덮어쓰지 않는다. 정리도 run이 만든 기기/임시 파일/fixture에만 적용한다. fixture backup 파일의 삭제는 테스트 소유 cleanup이며 제품 reset이 외부 백업을 지운 것으로 해석하지 않는다. + +### 실제 전달·오프라인·바이너리 경계 + +- 예약 시각, 실제 수신 시각, native provider, display/timing 권한, 앱 lifecycle을 기록한다. Android approximate/inexact 지연을 exact 성공으로 표시하지 않고 A13 정책대로 관측한다. iOS AlarmKit capability/permission과 fallback을 구별해 실제 provider를 기록한다. +- watchdog은 유한해야 한다. 초과하면 `fail` 또는 구체적 환경 원인을 가진 `blocked`로 기록하며 미수신을 pass로 바꾸지 않는다. fake clock·callback·무제한 재시도로 green을 만들지 않는다. +- 자동 emulator/simulator smoke의 debug transport는 허용된 개발 도구 경계일 뿐 release의 Product Network Boundary 증거가 아니다. 호스트 네트워크 설정을 바꾸지 않는다. +- 물리 release candidate에서 첫 실행 전 airplane mode 및 Wi-Fi/cellular가 다시 활성화되지 않았는지 확인한다. 캐시/다운로드 warm-up 없이 onboarding·local data·delivery·backup·restore·reset이 동작해야 한다. +- 비행기 모드 동작은 “트래픽이 발생할 수 없다”의 단독 증거가 아니다. 최종 APK/IPA 권한·SDK·entitlement·별도 트래픽 관측은 R09와 연결한다. 외부 file provider는 local/offline 저장 대상으로 선택하고 cloud sync를 완료했다고 주장하지 않는다. + +## 실행 행렬 + +각 행을 Android/iOS 각각 기록한다. 아래 모두 **이번 문서 작성 기준 미실행**이며 자동·native driver·수동 경로는 구현할 때 명시한다. 하나의 여정이 여러 행을 입증하면 동일 run/evidence를 연결하고 불필요한 중복 suite를 만들지 않는다. + +| ID | 실제 행동 | 독립 판정 및 증거 | 실행 경계 | +|---|---|---|---| +| J01 | 새 소유 QA 환경/앱 설치, 첫 시작 | target/run ID, 이전 data/secure-store가 없는 환경, 정상 startup/DI 진입 | emulator/simulator smoke + 물리 RC | +| J02 | 연결 warm-up 없이 offline onboarding | Local Profile·기본 준비 저장, 홈 진입, 필요한 문구/폰트/아이콘 표시, 서버 로그인 요구 없음 | 물리 RC에서 airplane/Wi-Fi/cellular 상태 기록 | +| J03 | 실제 폼으로 단일 미래 일정·전용 준비 저장 | 이름·civil time·시간대·장소·단계 순서/시간을 검토/상세에서 독립 fixture와 비교 | 양 플랫폼 자동 core | +| J04 | 미래 일정 편집 후 다시 읽기 | 변경한 필드 반영, 변경하지 않은 필드 보존, 중복 일정 없음 | 양 플랫폼 자동 core | +| J05 | 별도 미래 fixture 삭제 | 삭제 확인→목록/상세에서 부재, 기존 보존 fixture 유지 | 양 플랫폼 자동 core; U01 계약 준수 | +| J06 | OS 전달 허용 상태에서 실제 미래 예약 | 실제 provider/권한mode와 예약 시각 기록, 등록부는 보조 근거로만 사용 | T02 native 경로 재사용 | +| J07 | 앱을 background로 두고 실제 수신 대기 | OS 배너/알림/AlarmKit UI의 실제 관측과 수신 시각, 비공개 기본 문구; watchdog 미수신은 실패/환경기록 | 물리 RC 필수; simulator 관측은 별도 | +| J08 | 실제 OS 알림을 탭해 앱 진입 | 대상 schedule prompt/상세 일치, 초기 launch/router 연결, 중복 진행 시작 없음 | native driver 또는 수동; A12 연계 | +| J09 | 실제 시작→진행→명시 완료 | A07 start receipt 이후 진행, 입력한 단계와 독립 시간 기대값, 완료 상태·기록 유지 | 실제 UI/timer; 자동 core + 물리 RC | +| J10 | 완료 후 외부 runner로 프로세스 종료→같은 설치 cold launch | 새 PID, Local Profile/보존 일정/완료 이력의 UI 재조회, duplicate/유실 없음 | 양 플랫폼 자동 + 물리 RC | +| J11 | 내 데이터에서 비밀번호 입력→native local 파일 저장 | 실제 picker 성공, 암호문 파일 확보, freshness/사용자 결과 확인; 비밀번호/키는 증거에 기록 안 함 | 양 플랫폼 native 성공 여정 | +| J12 | backup 이후 구별되는 추가 일정/편집을 UI로 저장 | 원본 cutoff와 현재 상태 차이를 독립 fixture로 확정 | 복원이 실제 replace인지 판정할 준비 | +| J13 | native 파일 선택→비밀번호→Restore Preview | 실제 저장 파일 인증, cutoff/count/category 표시 확인, 최종 확인 전 현재 데이터 불변 | A02/A03/D02 연계 | +| J14 | 명시 replacement 확인→완료→재실행/재조회 | backup cutoff의 durable 상태 복원, J12 이후 데이터가 섞이지 않음; source key/OS IDs/runtime 복원 주장 없음 | 양 플랫폼 성공 여정; A09 연계 | +| J15 | Local Data Reset 명시 확인→완료→onboarding | 이전 profile/data/runtime 접근 불가, OS cleanup의 실제 확정/부분 상태 구분, 외부 backup 파일 보존 | D03 성공 여정; 부분을 완료로 판정 금지 | +| J16 | reset 후 실제 프로세스 재시작→새 onboarding | 이전 데이터/알림이 되살아나지 않고 새 Local Profile 생성 가능 | 양 플랫폼 native + durable 확인 | +| J17 | release candidate에서 J02–J16의 offline 핵심 경로 연결 | source/build/digest/기기 및 실제 관측 증거 완비, debug smoke와 별도 상태 | 각 물리 플랫폼 필수 | +| J18 | 실패→진단→수정 후 동일 fixture 재실행 | 원 실패 원인/로그와 후속 결과 둘 다 보존, 실패 행 삭제/skip으로 성공률 조작 없음 | 제품 의존 이슈와 연결 | + +일정 CRUD는 진행 중 삭제 허용이나 완료 이력 삭제 숨김 같은 기존 잘못된 테스트 기대값을 재사용하지 않는다. U01/T04의 현재 계약과 함께 실행한다. 반복 일정 전체 조합, cross-platform backup 양방향, denied/fallback/AlarmKit/lockscreen/reboot/update/OEM 조합은 원 기능/T02의 행렬에 연결하며 이 표의 기본 여정을 생략하는 사유로 삼지 않는다. 백업/복원/초기화의 장애 변형은 T03의 기존 행렬을 중복 복사하지 않는다. + +## CI와 증거 산출물 + +1. 실제 app smoke의 재현 entrypoint, fixture, target 검사, 실행 명령, 결과 JSON schema, 스크린샷/로그 경로 및 제한을 저장소에 둔다. 구현 전 정확한 runner API/OS 지원은 잠긴 Flutter/plugin/native toolchain과 공식 문서로 확인하며, 이 이슈는 실행되지 않은 명령을 성공 로그처럼 제시하지 않는다. +2. 기존 Flutter Testing의 generator·analyze·unit/widget·80% coverage gate를 유지한다. 모바일 빌드/job wiring은 R02, 검증 소스와 배포 연결은 R04, enforcement는 R01과 연결한다. Android emulator와 iOS simulator의 결정적인 core app smoke를 PR 목표로 둔다. +3. 각 실행 결과에는 `sourceSha`, 필요 시 `mergeSha`, build mode, version/build, artifact digest, OS/기기/대상ID, locale, schedule/device time zone, fixture/run ID, 시작·종료 시간, permission/provider, expected/actual, assertion/evidence 위치, 사용한 injection, `pass|fail|blocked|not_run`을 기록한다. 실제 설치한 artifact와 다른 artifact digest를 연결하지 않는다. +4. native UI를 지원하지 않는 자동 경로는 driver 또는 명시 기기 행을 남긴다. skipped job이나 실행하지 않은 수동 절차는 pass가 아니다. hardware/signing이 없으면 정확한 행과 사유를 남기고 OPEN을 유지한다. +5. 실패 artifact를 `always` 등 적절한 경로로 보존하고 timeout·driver 오류·앱 오류를 구별한다. 원 실패와 재실행 결과를 모두 남긴다. 비밀번호, Installation Data Key, 실제 사용자 데이터, 서명 비밀은 출력하지 않는다. +6. 기존 929 host tests, packaged-manifest CI, 이전 반복 iOS QA는 해당 SHA와 증거 수준으로 연결한다. 기존 통과 수치를 T01의 신규 E2E 통과로 합산하지 않는다. CI 성공, 실제 기기 성공, PR 병합, store 배포는 별도 상태다. + +## 의존성과 실행 순서 + +- **선행 제품 안정성:** A01–A15 중 실제 delivery/start/run/복원·reset 경로, D01–D05의 startup/key/store/restore 안정성, U01의 삭제 계약. 현재 구현 상태를 다시 읽고 누락된 제품 결함은 원 이슈에서 해결한 뒤 동일 fixture로 재검증한다. +- **테스트 재사용:** [T03 #613](https://github.com/DevKor-github/OnTime-front/issues/613), [T04 #614](https://github.com/DevKor-github/OnTime-front/issues/614); T02는 native UI·권한/lockscreen·SQLCipher/Keychain·provider 조합을 소유한다. T01은 실제 app 성공 여정의 연결을 소유한다. +- **CI/출시 연결:** R02 native/mobile job, R04 같은 소스 품질·배포, R01 required checks, R09 최종 artifact와 offline/traffic. 미구현 CI 보호를 이미 적용됐다고 적지 않는다. +- 순서: 고정 소스/대상 manifest와 fixture → 정상 main app 자동 core → native picker/수신·탭 bridge → 실제 프로세스 재실행 → 양 플랫폼 PR job 연결 → 물리 RC offline 여정 → 원격 결과/실제 artifact read-back → 이슈 조건 평가. + +## 완료 기준 + +- [ ] 정상 main/startup/DI/암호화 저장소를 실행하는 재현 가능한 Android·iOS core app smoke가 있으며 실제 양 플랫폼 실행 기록이 있다. +- [ ] 새 설치 onboarding, 일정 생성·조회·편집·삭제, 실제 준비 시작·진행·완료, 실제 프로세스 재실행 후 durable 보존이 독립 fixture로 검증된다. +- [ ] 각 물리 플랫폼 release candidate의 Offline Cold Start, 실제 OS 수신, 실제 알림 탭, 재실행 증거가 source/build/digest 및 provider/권한과 연결된다. +- [ ] native 파일 저장·선택→완전한 preview→명시 replacement→readback, reset→onboarding 성공 여정이 양 플랫폼에서 실행됐다. +- [ ] 기존 사용자/다른 QA 데이터는 보존되며 runner의 설치·종료·네트워크·정리 동작이 자기 소유 target으로 제한된다. +- [ ] 실제 수신과 channel/registry 성공, 자동 smoke와 물리 RC, process relaunch와 repump, offline 동작과 network-boundary 증거가 구별된다. +- [ ] PR mobile smoke 및 결과 artifact가 R02/R04와 연결되고 동일 소스 실패를 숨기지 않는다. native 미지원/권한/서명/기기 부족은 명시 pending으로 남는다. +- [ ] 요구된 모든 행의 evidence와 원 실패/후속 결과가 있으며 제품 의존 결함은 수정 후 재검증됐다. 필수 물리 행이나 hardware/signing이 미충족이면 이 이슈를 닫지 않는다. + +제외: 서버·로그인·cloud analytics 추가, 실제 사용자 설치 초기화, 전체 OS/기기 조합의 중복 suite, 테스트 전용 DB bypass/키 노출/제품 debug-export API, store 제출·배포 성공 주장, 무조건적인 정확 시각 전달 보장, placeholder 삭제만으로 완료 선언. + +## 실제 grill-with-docs 전체 문답 + +아래는 전담 agent가 root에게 전달한 실제 질문·보충과 root 답변이다. root는 사용자를 대신해 결정하도록 명시 위임받았다. 질문별 채택 내용과 baseline 정정을 보존하며 새 합성 문답으로 바꾸지 않았다. + +### Q1 — 완료 범위와 기존 감사 정정 + +**Agent:** +> T01은 grill-with-docs를 읽고 e6c1438e 고정 소스를 조사했습니다. 감사의 ‘RunnerTests testExample placeholder’는 현재 사실이 아닙니다. A02가 malformed export XCTest로 교체했지만 실제 전체 앱 E2E는 여전히 없고, pubspec에 integration_test도 없습니다. Q1 — 핵심 E2E 완료 범위를 권고안대로 확정할까요? 권고: 실제 main→bootstrap→DI→암호화 저장소→UI→OS local delivery로 연결하는 양 모바일 공통 최소 journey(새 설치/offline onboarding, 일정·전용 준비 저장, 실제 OS 알림 수신·탭, 실제 진행/완료, 프로세스 재실행 후 durable 데이터 유지)를 T01의 필수로 두고, Android/iOS simulator/emulator 자동 smoke와 물리 기기 release-candidate Offline Cold Start/실제 수신을 분리합니다. integration_test가 조작 못 하는 권한창/lockscreen/native 알림 탭은 T02의 native UI 또는 수동 절차를 연결하고, 채널 성공·등록부·배너 없는 timeout을 수신 PASS로 하지 않습니다. backup→preview→replace restore 및 reset→onboarding의 실제 UI+저장소 각 1개 성공 journey도 ADR0024의 all core flows에 맞춰 T01에 포함하고, 장애 변형/crypto와 광범위 OS 조합은 T03/T02를 재사용합니다. 기존 iOS 반복 QA는 이전 SHA의 제한 증거로 연결하되 새 SHA 전체 E2E 완료로 재표시하지 않습니다. + +**Agent 조사 보충:** +> 추가 조사: 실제 lib/main.dart가 LocalStartupGate→bootstrapWithPrivacyCleanup→LocalDataLifecycle.bootstrap→configureDependencies→ResetAwareApp/App을 연결하고, App은 실제 OS 초기 launch와 lifecycle을 처리합니다. 단순 App만 pump하거나 DI fake로 교체한 suite는 이 구간을 빠뜨립니다. 기존 recurring iOS QA의 전용 simulator는 데이터 보존 대상으로 확인했고 T01 신규 fixture와 분리할 계획입니다. Q1 답변 기다리는 동안 고정 소스/기존 QA의 재사용 경계를 정리 중입니다. + +**Root:** +> Q1 권고 채택. baseline placeholder 주장은 A02 교체 사실로 정정하고 현재 부족한 실제 app E2E를 대상으로 하세요. main→startup gate/bootstrap/DI→실제 암호화 저장소→UI→OS 여정, 양 모바일 onboarding/offline CRUD·진행완료·프로세스 재실행 durable 보존을 필수로. backup 생성→native 파일 선택→preview→명시 replace→readback와 reset→onboarding도 성공1journey씩 포함. OS 알림은 실제 수신과 실제 탭의 관측 필요하며 plugin/registry·timeout을 pass로 간주 금지. emulator/simulator 자동 smoke와 물리 release-candidate Offline Cold Start/수신은 별도이며 전체 기기행렬 T02, 장애/암호문 T03 재사용. 기존 사용자/다른 QA simulator 데이터는 건드리지 않는 새 disposable synthetic fixture만 사용. 기존 이전 SHA QA는 해당 한계로 링크, 새로운 전체 E2E 근거로 재표시 금지. 실제 앱 정상 DI 경로가 기본이며 테스트 편의 injection이 필요하면 clock/외부경계만 명시하고 제품workflow/mock DB로 바꾸지 않음. + +### Q2 — 격리·실제 재실행·시간 판정 + +**Agent:** +> T01 Q2 — 자동 실행의 isolation/재실행/시간 판정 계약을 정하겠습니다. 권고: runner가 먼저 자기 소유의 새 disposable Android AVD/iOS simulator ID·앱 ID·run ID를 생성/기록하고 그 대상으로만 설치·permission·네트워크·종료·정리를 제한합니다. 기존 simulator C8BA159D-A97A-458B-A5FC-B04A8B6F2848과 연결된 사용자 기기는 자동 선택/삭제 금지. iOS는 단순 uninstall을 secure-store clean-install 증거로 추정하지 않고 새 simulator/전용 초기화된 QA 기기 설치 상태를 확인합니다. fixture 생성은 실제 onboarding/폼을 통하고 expected는 synthetic 입력의 독립 값으로 고정합니다. 화면만 재-pump하는 대신 외부 runner가 PID 종료→같은 설치 cold launch하고 profile/일정/완료 상태를 UI 재조회하며 DB fixture 주입/암호화 key 출력/앱 데이터 export 디버그 channel을 제품에 추가하지 않습니다. 실제 OS 수신 journey는 실제 wall clock/예약 시각/수신 시각/permission/provider를 기록하고 inexact 경로의 지연을 A13 정책대로 별도 관측합니다. 유한 watchdog 초과는 ‘미수신/실패 또는 환경 문제’로 남기고 임의 fakeclock/delivery callback으로 통과시키지 않습니다. debug 자동화 transport는 release Offline Cold Start 증거로 쓰지 않고, release 후보는 호스트 네트워크 변경 없이 지정 QA 기기 airplane mode(Wi-Fi·cellular 재활성화 여부 포함)를 확인합니다. 이 isolation 및 진짜 프로세스 재실행 계약으로 확정할까요? + +**Root:** +> Q2 채택. runner 소유 새 disposable AVD/simulator 및 run ID·대상ID 명시/검증, 기존 simulator와 사용자 기기 자동선택/정리 금지. 기존 hardware는 향후 명시 지정 QA 대상으로만 진행. 새 simulator로 secure-store clean install 확인, uninstall만으로 키제거 가정금지. fixture는 실제 UI 입력/독립 expected; 외부 runner PID 종료→동일 설치 cold launch, repump=재실행 아님. 키출력/DB주입/제품debug-export channel 금지. 실제 OS 수신은 wall clock·예약/수신시각·provider/권한기록, inexact 지연관측 분리, watchdog 초과를 callback/fakeclock으로 통과시키지 않음. debug transport와 release offline proof 구별, 호스트 네트워크 손대지 않고 지정 QA 기기 airplane/WiFi/cellular 상태 명시. 종료/정리도 자기소유 대상만; 실험 실패 로그/스크린샷은 synthetic 데이터만 보존하며 재시도 전 원실패를 덮어쓰지 마세요. + +### Q3 — CI·증거·종결 + +**Agent:** +> T01 Q3 — CI·증거·종결 기준을 권고대로 확정할까요? 권고: 기존 Flutter Testing의 generator/analyze/unit/coverage를 재사용하고 Android/iOS 실제 app smoke의 재현 entrypoint·결과 JSON·증거 업로드를 T01에서 만들며, PR mobile build/job wiring은 R02, 동일 SHA 품질/배포 연결은 R04, 필수검사 enforcement는 R01, 최종 artifact/no-network는 R09와 연결합니다. Android emulator+iOS simulator의 deterministic core app smoke는 각 PR에서 실행할 목표로 두되 OS 알림/권한/native picker 등 지원안되는 행은 명시 native driver/수동 기기 행으로 남기고 skip된 job을 전체 성공으로 취급하지 않습니다. 결과별 source SHA(+merge SHA), build mode/version/digest, OS/device/locale/time zone, fixture/run ID, 시작·종료·기대/실제·assertion/evidence, injection, status(pass/fail/blocked/not_run)를 기록하고 실패 원본을 보존합니다. baseline의 929 host tests/Android packaged-manifest CI와 기존 recurring simulator QA는 각 범위로만 재사용합니다. T01 완료는 두 플랫폼 core journey 자동실행, 각 물리 release-candidate 오프라인/실제 수신·탭·재실행, 성공 backup/restore/reset 증거까지 충족해야 하며 hardware/signing 미확보는 OPEN으로 둡니다. provider denied/AlarmKit/fallback/lockscreen·update/reboot 세부 조합은 T02 등 원 이슈에 남기되, T01 기본 여정의 결함을 단순 제외하고 green으로 닫지는 않습니다. 아직 key/data migration/startup/restore 제품 결함이면 D01-D05/A계열로 해결하고 같은 fixture 재실행합니다. 테스트 infra를 위한 새 도메인 용어/ADR는 추가하지 않는 안입니다. + +**Root:** +> Q3 채택. T01 재현가능한 실제 app smoke entrypoint/결과/증거, R02 mobile CI wiring, R04 동일 검증소스-배포연결,R01 enforcement,R09최종artifact/offline 명확히 연결. 두 플랫폼 deterministic smoke는 PR목표이며 native UI 미지원은 driver/명시 기기행으로 남기고 skip은 pass 아님. SHA/source+merge·buildmode/version/digest·OS/device/locale/zone·fixture/runID·시간·expected/actual·assertions/evidence·injection·pass/fail/blocked/not_run을 기록. 제품결함은 원이슈수정후 동일fixture 재검증, green위해행삭제금지. 초기 원실패와 retry결과 둘다 보존. T01 완료 조건은 제시한 양 플랫폼 자동core+물리RCoffline/수신탭/재실행+backup/restore/reset 성공증거, hardware/signing미확보는OPEN. 기본core행렬에 provider/권한mode를 명시하되 모든조합복제는T02재사용. 새glossary/ADR없음. 충분하면 실제 전체문답과 현재baseline정정·고정근거·실행행렬·의존성 포함 T01.md 동결해주세요. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 40b66d7c..584c0a11 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -780,16 +780,18 @@ "핵심 실제 app E2E를 추가한다. 현재 integration_test 디렉터리가 없고 iOS RunnerTests는 testExample placeholder다.", "비행기 모드 새 설치→온보딩→일정 저장→실제 알림→진행→완료→재실행의 데이터 유지. 양 모바일 플랫폼 실행 기록." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_t01", + "issue_number": 615, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/615", "decisions_file": "plans/audit-2026-09-23/issues/T01.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 28 + "sequence": 28, + "readback_verified": true, + "issue_body_sha256": "9de244dd9cae1917d7a180e4180653ddc2f4d4adc8b9536be82115c62276d878" }, { "audit_id": "T02", From d73c7863a01343004ccadcd72064b563c464dd0d Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:28:37 +0900 Subject: [PATCH 42/59] fix: make preparation start atomic and recoverable --- ...early_start_session_local_data_source.dart | 4 +- .../schedule_repository_impl.dart | 24 +- .../entities/schedule_start_rejected.dart | 5 + .../repositories/schedule_repository.dart | 2 +- .../use-cases/finish_schedule_use_case.dart | 7 +- ...schedule_preparation_session_use_case.dart | 284 ++++++++++++++--- lib/l10n/app_en.arb | 4 +- lib/l10n/app_ko.arb | 4 +- lib/l10n/app_localizations.dart | 12 + lib/l10n/app_localizations_en.dart | 8 + lib/l10n/app_localizations_ko.dart | 7 + .../components/alarm_screen_top_section.dart | 4 +- .../components/preparation_step_tile.dart | 18 +- .../alarm/screens/alarm_screen.dart | 26 ++ .../alarm/screens/schedule_start_screen.dart | 79 +++-- .../app/bloc/schedule/schedule_bloc.dart | 211 +++++++++++-- .../app/bloc/schedule/schedule_event.dart | 8 +- .../app/bloc/schedule/schedule_state.dart | 11 + .../schedule_start_transaction_test.dart | 244 +++++++++++++++ .../schedule_mutation_use_cases_test.dart | 7 +- ...ule_preparation_session_use_case_test.dart | 294 +++++++++++++++++- .../timed_preparation_use_cases_test.dart | 5 +- .../screens/preparation_flow_widget_test.dart | 71 ++++- .../schedule_start_commit_widget_test.dart | 292 +++++++++++++++++ .../preparation_start_recovery_test.dart | 123 ++++++++ .../preparation_step_delivery_test.dart | 1 + .../app/bloc/schedule/schedule_bloc_test.dart | 8 +- .../notification_launch_widget_test.dart | 5 +- tool/qa/capture_preparation_start.dart | 144 +++++++++ 29 files changed, 1789 insertions(+), 123 deletions(-) create mode 100644 lib/domain/entities/schedule_start_rejected.dart create mode 100644 test/data/repositories/schedule_start_transaction_test.dart create mode 100644 test/presentation/alarm/screens/schedule_start_commit_widget_test.dart create mode 100644 test/presentation/app/bloc/schedule/preparation_start_recovery_test.dart create mode 100644 tool/qa/capture_preparation_start.dart diff --git a/lib/data/data_sources/early_start_session_local_data_source.dart b/lib/data/data_sources/early_start_session_local_data_source.dart index c960ab32..2baf6225 100644 --- a/lib/data/data_sources/early_start_session_local_data_source.dart +++ b/lib/data/data_sources/early_start_session_local_data_source.dart @@ -27,7 +27,9 @@ class EarlyStartSessionLocalDataSourceImpl final prefs = await SharedPreferences.getInstance(); final key = '$_prefsKeyPrefix$scheduleId'; final payload = jsonEncode({'startedAt': startedAt.millisecondsSinceEpoch}); - await prefs.setString(key, payload); + if (!await prefs.setString(key, payload)) { + throw StateError('Early preparation start was not saved'); + } } @override diff --git a/lib/data/repositories/schedule_repository_impl.dart b/lib/data/repositories/schedule_repository_impl.dart index 349f4061..9a282ec2 100644 --- a/lib/data/repositories/schedule_repository_impl.dart +++ b/lib/data/repositories/schedule_repository_impl.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/schedule_start_rejected.dart'; import 'dart:async'; import 'package:collection/collection.dart'; @@ -87,17 +88,30 @@ class ScheduleRepositoryImpl implements ScheduleRepository { } @override - Future startSchedule(String scheduleId) async { - final existing = await _scheduleDao.getScheduleById(scheduleId); + Future startSchedule( + String scheduleId, { + DateTime? startedAt, + }) => _database.transaction(() async { + final existing = (await _scheduleDao.getScheduleById(scheduleId)).schedule; + if (existing.doneStatus != ScheduleDoneStatus.notEnded.name) { + throw ScheduleStartRejected(scheduleId); + } + final firstStartedAt = existing.startedAt ?? startedAt ?? DateTime.now(); + if (existing.isStarted && + existing.startedAt != null && + existing.preparationFrozen) { + return firstStartedAt; + } await _scheduleDao.updateSchedule( - existing.schedule.copyWith( + existing.copyWith( isStarted: true, - startedAt: Value(DateTime.now()), + startedAt: Value(firstStartedAt), preparationFrozen: true, ), ); await _userDao.markDurableDataChanged(localProfileId); - } + return firstStartedAt; + }); @override Future getScheduleById(String id) async { diff --git a/lib/domain/entities/schedule_start_rejected.dart b/lib/domain/entities/schedule_start_rejected.dart new file mode 100644 index 00000000..00e3c55f --- /dev/null +++ b/lib/domain/entities/schedule_start_rejected.dart @@ -0,0 +1,5 @@ +/// A completed occurrence cannot acquire another durable start transition. +final class ScheduleStartRejected implements Exception { + const ScheduleStartRejected(this.scheduleId); + final String scheduleId; +} diff --git a/lib/domain/repositories/schedule_repository.dart b/lib/domain/repositories/schedule_repository.dart index 374e4226..020d4c13 100644 --- a/lib/domain/repositories/schedule_repository.dart +++ b/lib/domain/repositories/schedule_repository.dart @@ -39,7 +39,7 @@ abstract interface class ScheduleRepository { Future deleteSchedule(ScheduleEntity schedule); /// Start a schedule preparation session - Future startSchedule(String scheduleId); + Future startSchedule(String scheduleId, {DateTime? startedAt}); /// Finish a schedule with lateness time Future finishSchedule(String scheduleId, int latenessTime); diff --git a/lib/domain/use-cases/finish_schedule_use_case.dart b/lib/domain/use-cases/finish_schedule_use_case.dart index 6596b38c..04dee17d 100644 --- a/lib/domain/use-cases/finish_schedule_use_case.dart +++ b/lib/domain/use-cases/finish_schedule_use_case.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/schedule_start_rejected.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/domain/repositories/schedule_repository.dart'; import 'package:on_time_front/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart'; @@ -13,7 +14,11 @@ class FinishScheduleUseCase { ); Future call(String scheduleId, int latenessTime) async { - await _scheduleRepository.startSchedule(scheduleId); + try { + await _scheduleRepository.startSchedule(scheduleId); + } on ScheduleStartRejected { + // Repeated finish remains idempotent; start itself rejects ended runs. + } await _scheduleRepository.finishSchedule(scheduleId, latenessTime); await _alarmEffectsCoordinator( operation: ScheduleMutationAlarmOperation.finished, diff --git a/lib/domain/use-cases/schedule_preparation_session_use_case.dart b/lib/domain/use-cases/schedule_preparation_session_use_case.dart index 5585cbe8..6148ca8d 100644 --- a/lib/domain/use-cases/schedule_preparation_session_use_case.dart +++ b/lib/domain/use-cases/schedule_preparation_session_use_case.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/schedule_start_rejected.dart'; import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:on_time_front/core/logging/app_logger.dart'; import 'package:on_time_front/domain/entities/schedule_not_found.dart'; @@ -56,8 +57,11 @@ class SchedulePreparationSessionUseCase { this._timedPreparationRepository, this._earlyStartSessionRepository, this._cancelScheduleAlarmUseCase, - this._reconcileAlarmsUseCase, - ); + this._reconcileAlarmsUseCase, { + @ignoreParam AlarmOperationCoordinator? operations, + }) : _operations = operations ?? AlarmOperationCoordinator.shared { + _operations.addListener(_discardOldGenerations); + } final ScheduleRepository _scheduleRepository; final PreparationRepository _preparationRepository; @@ -65,30 +69,173 @@ class SchedulePreparationSessionUseCase { final EarlyStartSessionRepository _earlyStartSessionRepository; final CancelScheduleAlarmUseCase _cancelScheduleAlarmUseCase; final ReconcileAlarmsUseCase _reconcileAlarmsUseCase; - final Set _startedScheduleIds = {}; + final AlarmOperationCoordinator _operations; + final _starts = <(int, String), Future>{}; + final _runs = <(int, String), _StartRun>{}; + final _projections = <(int, String), TimedPreparationSnapshotEntity>{}; + final _epochs = <(int, String), int>{}; + + @disposeMethod + void dispose() { + _operations.removeListener(_discardOldGenerations); + _starts.clear(); + _runs.clear(); + _projections.clear(); + _epochs.clear(); + } + + void _discardOldGenerations() { + final generation = _operations.generation; + // Queued futures keep their own completion/lease; dropping lookup entries + // never cancels their native work or releases the shared operation owner. + _starts.removeWhere((key, _) => key.$1 != generation); + _runs.removeWhere((key, _) => key.$1 != generation); + _projections.removeWhere((key, _) => key.$1 != generation); + _epochs.removeWhere((key, _) => key.$1 != generation); + } - Future startEarlySession( + Future startEarlySession( ScheduleWithPreparationEntity schedule, { required DateTime startedAt, - }) async { - await _earlyStartSessionRepository.markStarted( - scheduleId: schedule.id, - startedAt: startedAt, + bool Function()? isCurrent, + }) { + final lease = _operations.capture(); + final key = (lease.generation, schedule.id); + if (_runs[key] != null && + _runs[key]!.fingerprint != schedule.cacheFingerprint) { + _runs.remove(key); + _projections.remove(key); + } + final run = _runs.putIfAbsent( + key, + () => _StartRun(startedAt, schedule.cacheFingerprint), ); - await startSchedulePreparation(schedule.id); + if (run.flight != null) return run.flight!; + final future = _startEarly(schedule, run, lease, isCurrent); + run.flight = future; + future.then( + (_) { + run.flight = null; + }, + onError: (Object _, StackTrace __) { + run.flight = null; + }, + ); + return future; + } + + Future _startEarly( + ScheduleWithPreparationEntity schedule, + _StartRun run, + AlarmOperationLease lease, + bool Function()? isCurrent, + ) async { + final key = (lease.generation, schedule.id); + bool current() => + lease.isCurrent && + identical(_runs[key], run) && + (isCurrent?.call() ?? true); + if (!current()) throw const AlarmOperationInvalidated(); + await _operations.run(lease, () async { + if (!current()) throw const AlarmOperationInvalidated(); + if (!run.initialized) { + final stored = await _timedPreparationRepository + .getTimedPreparationSnapshot(schedule.id); + if (!current()) throw const AlarmOperationInvalidated(); + final validated = stored == null + ? null + : validatePreparationSnapshot(stored, schedule); + // A view owner is not a Preparation Run identity. Only a validated + // active snapshot resumes a previous run; DB startedAt alone never does. + if (validated != null && + !validated.requiresConfirmation && + validated.startedAt != null) { + run.startedAt = validated.startedAt!; + _projections[key] = validated; + } + run.initialized = true; + } + if (!run.committed) { + run.durableStartedAt = await _scheduleRepository.startSchedule( + schedule.id, + startedAt: run.startedAt, + ); + run.committed = true; + } else { + final existing = await _scheduleRepository.getScheduleById(schedule.id); + if (_isEnded(existing.doneStatus)) { + throw ScheduleStartRejected(schedule.id); + } + } + }); + if (!current()) throw const AlarmOperationInvalidated(); + var partial = false; + try { + await _operations.run(lease, () async { + if (!current()) throw const AlarmOperationInvalidated(); + await _earlyStartSessionRepository.markStarted( + scheduleId: schedule.id, + startedAt: run.startedAt, + ); + }); + } on AlarmOperationInvalidated { + rethrow; + } catch (_) { + partial = true; + } + if (!current()) throw const AlarmOperationInvalidated(); + try { + // A retry uses the latest projection, never the initial empty event list. + if (!_projections.containsKey(key)) { + _projections[key] = TimedPreparationSnapshotEntity( + preparation: schedule.preparation, + savedAt: run.startedAt, + scheduleFingerprint: schedule.cacheFingerprint, + startedAt: run.startedAt, + ); + } + await _persistProjection(schedule.id, lease, isCurrent: current); + } on AlarmOperationInvalidated { + rethrow; + } catch (_) { + partial = true; + } + if (!current()) throw const AlarmOperationInvalidated(); + try { + await _cancelScheduleAlarmUseCase(schedule.id); + } on AlarmOperationInvalidated { + rethrow; + } catch (_) { + partial = true; + } + if (!current()) throw const AlarmOperationInvalidated(); requestAlarmReconciliation(_reconcileAlarmsUseCase); - if (!await _cancelDelivery(schedule.id)) return; - await saveTimedPreparationSnapshot( - schedule, - savedAt: startedAt, - startedAt: startedAt, - actionEvents: const [], + return PreparationStartReceipt( + startedAt: run.startedAt, + durableStartedAt: run.durableStartedAt, + actionEvents: _projections[key]?.actionEvents ?? const [], + hasPendingRecovery: partial, ); } - Future startSchedulePreparation(String scheduleId) async { - if (!_startedScheduleIds.add(scheduleId)) return; - await _scheduleRepository.startSchedule(scheduleId); + Future startSchedulePreparation(String scheduleId) { + final lease = _operations.capture(); + final key = (lease.generation, scheduleId); + final pending = _starts[key]; + if (pending != null) return pending; + final future = _operations.run(lease, () async { + await _scheduleRepository.startSchedule(scheduleId); + }); + _starts[key] = future; + future.then( + (_) { + if (identical(_starts[key], future)) _starts.remove(key); + }, + onError: (Object _, StackTrace __) { + if (identical(_starts[key], future)) _starts.remove(key); + }, + ); + return future; } Future hasEarlyStartSession(String scheduleId) async { @@ -104,6 +251,7 @@ class SchedulePreparationSessionUseCase { DateTime? savedAt, DateTime? startedAt, List actionEvents = const [], + bool persist = true, }) { final snapshot = TimedPreparationSnapshotEntity( preparation: schedule.preparation, @@ -112,10 +260,30 @@ class SchedulePreparationSessionUseCase { startedAt: startedAt, actionEvents: actionEvents, ); - return _timedPreparationRepository.saveTimedPreparationSnapshot( - schedule.id, - snapshot, - ); + final lease = _operations.capture(); + _projections[(lease.generation, schedule.id)] = snapshot; + return persist + ? _persistProjection(schedule.id, lease) + : Future.value(); + } + + Future _persistProjection( + String scheduleId, + AlarmOperationLease lease, { + bool Function()? isCurrent, + }) { + final key = (lease.generation, scheduleId); + final epoch = _epochs[key] ?? 0; + return _operations.run(lease, () async { + if ((_epochs[key] ?? 0) != epoch || !(isCurrent?.call() ?? true)) return; + final latest = _projections[key]; + if (latest != null) { + await _timedPreparationRepository.saveTimedPreparationSnapshot( + scheduleId, + latest, + ); + } + }); } Future restoreTimedPreparationIfValid( @@ -124,19 +292,24 @@ class SchedulePreparationSessionUseCase { RestoredSessionCallback? onRestoredSession, void Function()? onInvalidated, }) async { + final lease = _operations.capture(); final stored = await _timedPreparationRepository .getTimedPreparationSnapshot(schedule.id); + lease.check(); if (stored == null) return schedule; final snapshot = validatePreparationSnapshot(stored, schedule); if (snapshot == null || snapshot.requiresConfirmation) { await clearPersistedState(schedule.id); - await _timedPreparationRepository.saveTimedPreparationSnapshot( - schedule.id, - TimedPreparationSnapshotEntity( - preparation: schedule.preparation, - savedAt: now, - scheduleFingerprint: schedule.cacheFingerprint, - requiresConfirmation: true, + await _operations.run( + lease, + () => _timedPreparationRepository.saveTimedPreparationSnapshot( + schedule.id, + TimedPreparationSnapshotEntity( + preparation: schedule.preparation, + savedAt: now, + scheduleFingerprint: schedule.cacheFingerprint, + requiresConfirmation: true, + ), ), ); onInvalidated?.call(); @@ -163,18 +336,36 @@ class SchedulePreparationSessionUseCase { ); } - Future clearPersistedState(String scheduleId) async { - await _timedPreparationRepository.clearTimedPreparation(scheduleId); - await _earlyStartSessionRepository.clear(scheduleId); - _startedScheduleIds.remove(scheduleId); + Future clearPersistedState(String scheduleId) { + final lease = _operations.capture(); + final key = (lease.generation, scheduleId); + _runs.remove(key); + _projections.remove(key); + _epochs[key] = (_epochs[key] ?? 0) + 1; + return _operations.run(lease, () async { + await _timedPreparationRepository.clearTimedPreparation(scheduleId); + await _earlyStartSessionRepository.clear(scheduleId); + }); } Future finishSchedulePreparation( String scheduleId, { required int latenessTime, }) async { - await startSchedulePreparation(scheduleId); - await _scheduleRepository.finishSchedule(scheduleId, latenessTime); + final lease = _operations.capture(); + final key = (lease.generation, scheduleId); + _runs.remove(key); + _projections.remove(key); + _epochs[key] = (_epochs[key] ?? 0) + 1; + await _operations.run(lease, () async { + try { + await _scheduleRepository.startSchedule(scheduleId); + } on ScheduleStartRejected { + /* Already finished is idempotent. */ + } + await _scheduleRepository.finishSchedule(scheduleId, latenessTime); + }); + lease.check(); requestAlarmReconciliation(_reconcileAlarmsUseCase); if (!await _cancelDelivery(scheduleId)) return; await clearPersistedState(scheduleId); @@ -363,3 +554,26 @@ class SchedulePreparationSessionUseCase { ); } } + +class PreparationStartReceipt { + const PreparationStartReceipt({ + required this.startedAt, + this.durableStartedAt, + this.actionEvents = const [], + this.hasPendingRecovery = false, + }); + final DateTime? durableStartedAt; + final List actionEvents; + final DateTime startedAt; + final bool hasPendingRecovery; +} + +class _StartRun { + _StartRun(this.startedAt, this.fingerprint); + final String fingerprint; + DateTime startedAt; + DateTime? durableStartedAt; + bool initialized = false; + bool committed = false; + Future? flight; +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7b5dc508..a1982d7a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -624,5 +624,7 @@ "notificationIncompleteStatus": "Notification scheduling incomplete or needs checking", "notificationTimingSettings": "Timing settings", "startupLoadingBody": "Checking the data stored on this device.", - "startupWaitingBody": "Waiting for the device to respond. Please wait until the check finishes." + "startupWaitingBody": "Waiting for the device to respond. Please wait until the check finishes.", + "preparationStartFailed": "Preparation could not be started. Try again.", + "preparationStartPartial": "Preparation has started. Some progress storage or alarm cleanup is incomplete." } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index e961cd44..5ea27741 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -221,5 +221,7 @@ "notificationIncompleteStatus": "알림 예약 일부 실패 또는 확인 필요", "notificationTimingSettings": "정확한 시각 설정하기", "startupLoadingBody": "기기에 저장된 데이터를 확인하고 있습니다.", - "startupWaitingBody": "기기의 응답을 기다리고 있습니다. 확인이 끝날 때까지 잠시 기다려 주세요." + "startupWaitingBody": "기기의 응답을 기다리고 있습니다. 확인이 끝날 때까지 잠시 기다려 주세요.", + "preparationStartFailed": "준비 시작을 저장하지 못했어요. 다시 시도해주세요.", + "preparationStartPartial": "준비는 시작했지만 진행 저장 또는 알림 정리가 일부 완료되지 않았어요." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 14564335..17a955e0 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1063,6 +1063,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Waiting for the device to respond. Please wait until the check finishes.'** String get startupWaitingBody; + + /// No description provided for @preparationStartFailed. + /// + /// In en, this message translates to: + /// **'Preparation could not be started. Try again.'** + String get preparationStartFailed; + + /// No description provided for @preparationStartPartial. + /// + /// In en, this message translates to: + /// **'Preparation has started. Some progress storage or alarm cleanup is incomplete.'** + String get preparationStartPartial; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0e283fe4..465a1385 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -569,4 +569,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get startupWaitingBody => 'Waiting for the device to respond. Please wait until the check finishes.'; + + @override + String get preparationStartFailed => + 'Preparation could not be started. Try again.'; + + @override + String get preparationStartPartial => + 'Preparation has started. Some progress storage or alarm cleanup is incomplete.'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 89b113fa..2eb292e9 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -529,4 +529,11 @@ class AppLocalizationsKo extends AppLocalizations { @override String get startupWaitingBody => '기기의 응답을 기다리고 있습니다. 확인이 끝날 때까지 잠시 기다려 주세요.'; + + @override + String get preparationStartFailed => '준비 시작을 저장하지 못했어요. 다시 시도해주세요.'; + + @override + String get preparationStartPartial => + '준비는 시작했지만 진행 저장 또는 알림 정리가 일부 완료되지 않았어요.'; } diff --git a/lib/presentation/alarm/components/alarm_screen_top_section.dart b/lib/presentation/alarm/components/alarm_screen_top_section.dart index e539c433..e14ff91c 100644 --- a/lib/presentation/alarm/components/alarm_screen_top_section.dart +++ b/lib/presentation/alarm/components/alarm_screen_top_section.dart @@ -89,8 +89,8 @@ class _AlarmGraphSection extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - height: 190, + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: 190), child: Stack( alignment: Alignment.center, children: [ diff --git a/lib/presentation/alarm/components/preparation_step_tile.dart b/lib/presentation/alarm/components/preparation_step_tile.dart index 41c824bc..c94ccdf5 100644 --- a/lib/presentation/alarm/components/preparation_step_tile.dart +++ b/lib/presentation/alarm/components/preparation_step_tile.dart @@ -48,9 +48,8 @@ class PreparationStepTile extends StatelessWidget { if (preparationStepState == PreparationStateEnum.now && onSkip != null) { skipButton = Align( alignment: Alignment.centerRight, - child: SizedBox( - width: 326, - height: 53, + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 53, maxWidth: 326), child: TextButton( style: TextButton.styleFrom( backgroundColor: colorScheme.primaryContainer, @@ -81,11 +80,13 @@ class PreparationStepTile extends StatelessWidget { curve: Curves.ease, child: Container( width: 358, - height: - (preparationStepState == PreparationStateEnum.now && - skipButton != null) - ? 135 - : 62, + constraints: BoxConstraints( + minHeight: + (preparationStepState == PreparationStateEnum.now && + skipButton != null) + ? 135 + : 62, + ), decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(10)), border: (preparationStepState == PreparationStateEnum.now) @@ -96,6 +97,7 @@ class PreparationStepTile extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(12), child: Column( + mainAxisSize: MainAxisSize.min, children: [ Row( children: [ diff --git a/lib/presentation/alarm/screens/alarm_screen.dart b/lib/presentation/alarm/screens/alarm_screen.dart index 5d87fc55..31ecc506 100644 --- a/lib/presentation/alarm/screens/alarm_screen.dart +++ b/lib/presentation/alarm/screens/alarm_screen.dart @@ -280,6 +280,7 @@ class _AlarmScreenState extends State { } final alarmTheme = _buildAlarmTheme(context, isLateContinueMode); + final startState = context.watch().state; return Theme( key: const ValueKey('alarm_screen_theme'), @@ -288,6 +289,31 @@ class _AlarmScreenState extends State { builder: (context) { return Scaffold( backgroundColor: Theme.of(context).colorScheme.primary, + bottomNavigationBar: startState.hasPendingStartRecovery + ? SafeArea( + child: MaterialBanner( + content: Text(l10n.preparationStartPartial), + actions: [ + TextButton( + onPressed: startState.isRecoveringStart + ? null + : () => context.read().add( + const SchedulePreparationRecoveryRequested(), + ), + child: startState.isRecoveringStart + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : Text(l10n.startupRetryAction), + ), + ], + ), + ) + : null, body: Stack( children: [ Column( diff --git a/lib/presentation/alarm/screens/schedule_start_screen.dart b/lib/presentation/alarm/screens/schedule_start_screen.dart index c6ee77a4..5de28a32 100644 --- a/lib/presentation/alarm/screens/schedule_start_screen.dart +++ b/lib/presentation/alarm/screens/schedule_start_screen.dart @@ -155,18 +155,33 @@ class _ScheduleStartScreenState extends State { return l10n.preparationStartsEarlyBy(formattedLeadTime); } - void _onPrimaryActionPressed( + bool _starting = false; + bool _failed = false; + + Future _onPrimaryActionPressed( BuildContext context, ScheduleStartPromptVariant variant, - ) { - if (!(widget.isCurrent?.call() ?? true)) return; - widget.onExplicitStart?.call(); - if (widget.requiresExplicitStart || - variant == ScheduleStartPromptVariant.earlyStart || - variant == ScheduleStartPromptVariant.alarm) { - context.read().add(const SchedulePreparationStarted()); + ) async { + bool current() => + mounted && + (widget.isCurrent?.call() ?? true) && + ModalRoute.of(context)?.isCurrent == true; + if (_starting || !current()) return; + setState(() { + _starting = true; + _failed = false; + }); + try { + final bloc = context.read(); + final result = await bloc.requestPreparationStart(isCurrent: current); + if (!context.mounted || !current() || result == null) return; + widget.onExplicitStart?.call(); + context.go('/alarmScreen'); + } catch (_) { + if (current()) setState(() => _failed = true); + } finally { + if (mounted) setState(() => _starting = false); } - context.go('/alarmScreen'); } @override @@ -228,6 +243,16 @@ class _ScheduleStartScreenState extends State { ), textAlign: TextAlign.center, ), + if (_failed) + Text( + AppLocalizations.of(context)!.preparationStartFailed, + key: const Key('preparation-start-error'), + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + textAlign: TextAlign.center, + ), + if (_starting) const LinearProgressIndicator(), SizedBox(height: imageTopGap), Expanded( child: Center( @@ -270,13 +295,15 @@ class _ScheduleStartScreenState extends State { return Align( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 358), - child: SizedBox( - width: double.infinity, - height: 57, + child: ConstrainedBox( + constraints: const BoxConstraints( + minWidth: double.infinity, + minHeight: 57, + ), child: ElevatedButton( - onPressed: () async { - _onPrimaryActionPressed(context, variant); - }, + onPressed: _starting + ? null + : () => _onPrimaryActionPressed(context, variant), child: Text( variant == ScheduleStartPromptVariant.earlyStart || variant == ScheduleStartPromptVariant.alarm @@ -312,20 +339,24 @@ class _ScheduleStartScreenState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox( - width: double.infinity, - height: 57, + ConstrainedBox( + constraints: const BoxConstraints( + minWidth: double.infinity, + minHeight: 57, + ), child: ElevatedButton( - onPressed: () async { - _onPrimaryActionPressed(context, variant); - }, + onPressed: _starting + ? null + : () => _onPrimaryActionPressed(context, variant), child: Text(AppLocalizations.of(context)!.startPreparingNow), ), ), const SizedBox(height: 12), - SizedBox( - width: double.infinity, - height: 57, + ConstrainedBox( + constraints: const BoxConstraints( + minWidth: double.infinity, + minHeight: 57, + ), child: ElevatedButton( onPressed: () async { context.go('/home'); diff --git a/lib/presentation/app/bloc/schedule/schedule_bloc.dart b/lib/presentation/app/bloc/schedule/schedule_bloc.dart index 8d38d01a..87485998 100644 --- a/lib/presentation/app/bloc/schedule/schedule_bloc.dart +++ b/lib/presentation/app/bloc/schedule/schedule_bloc.dart @@ -101,6 +101,7 @@ class ScheduleBloc extends Bloc { on(_onAlarmPromptRequested); on(_onScheduleStarted); on(_onPreparationStarted); + on(_onStartRecovery); on(_onTick); on( _onPreparationTimeRefreshRequested, @@ -119,6 +120,8 @@ class ScheduleBloc extends Bloc { Timer? _scheduleStartTimer; String? _currentScheduleId; String? _activeEarlyStartScheduleId; + Object? _explicitStartPending; + bool _finishing = false; Timer? _preparationTimer; DateTime? _lastSnapshotSavedAt; DateTime? _activePreparationRunStartedAt; @@ -262,7 +265,19 @@ class ScheduleBloc extends Bloc { ScheduleUpcomingReceived event, Emitter emit, ) async { - if (notificationPreparationId != null) return; + if (notificationPreparationId != null || _explicitStartPending != null) { + return; + } + // A repository emission is not a successful recovery receipt. Preserve + // the live projection and pending warning for the same unchanged run. + if (state.hasPendingStartRecovery && + event.upcomingSchedule?.id == state.schedule?.id && + event.upcomingSchedule?.cacheFingerprint == + state.schedule?.cacheFingerprint && + event.upcomingSchedule != null && + !_isEnded(event.upcomingSchedule!.doneStatus)) { + return; + } final current = _captureBackgroundValidity(); if (!current()) return; _stepRevision++; @@ -299,7 +314,7 @@ class ScheduleBloc extends Bloc { if (_notificationPromptOwner != null) return; final earlyStartSession = await _getEarlyStartSession(incoming.id); - final hasEarlyStartSession = earlyStartSession != null; + var hasEarlyStartSession = earlyStartSession != null; if (!current()) return; _snapshotInvalidated = false; @@ -308,6 +323,12 @@ class ScheduleBloc extends Bloc { isCurrent: current, ); if (!current()) return; + hasEarlyStartSession = + hasEarlyStartSession || + (_activePreparationRunStartedAt != null && + !_activePreparationRunStartedAt!.isAtSameMomentAs( + incoming.preparationStartTime, + )); if (!_snapshotInvalidated && !hasEarlyStartSession && incoming.preparationStartTime.isAfter(now)) { @@ -326,7 +347,7 @@ class ScheduleBloc extends Bloc { if (hasEarlyStartSession) { _activeEarlyStartScheduleId = resolvedSchedule.id; - _activePreparationRunStartedAt ??= earlyStartSession.startedAt; + _activePreparationRunStartedAt ??= earlyStartSession!.startedAt; if (resolvedSchedule.preparation.elapsedTime == Duration.zero && _activePreparationActionEvents.isEmpty) { resolvedSchedule = @@ -510,34 +531,127 @@ class ScheduleBloc extends Bloc { _startScheduleTimer(schedule); } + Future requestPreparationStart({ + required bool Function() isCurrent, + }) { + if (isClosed) return Future.value(null); + final receipt = Completer(); + add(SchedulePreparationStarted(receipt: receipt, isCurrent: isCurrent)); + return receipt.future; + } + Future _onPreparationStarted( SchedulePreparationStarted event, Emitter emit, ) async { - final current = _captureBackgroundValidity(); - if (!current()) return; + final generation = LocalDataOperationGate.shared.generation; + final revision = _notificationRevision; final schedule = state.schedule; - if (schedule == null) return; - if (_activeEarlyStartScheduleId == schedule.id) return; - + bool current() => + !isClosed && + !emit.isDone && + LocalDataOperationGate.shared.generation == generation && + _notificationRevision == revision && + state.schedule?.id == schedule?.id && + (event.isCurrent?.call() ?? _notificationPromptOwner == null); + if (schedule == null || !current()) { + event.receipt?.complete(null); + return; + } + if (_activeEarlyStartScheduleId == schedule.id && + _activePreparationRunStartedAt != null && + event.receipt == null) { + return; + } + final pending = Object(); + _explicitStartPending = pending; _currentScheduleId = schedule.id; - _activeEarlyStartScheduleId = schedule.id; _scheduleStartTimer?.cancel(); _scheduleStartTimer = null; + try { + final result = await _schedulePreparationSessionUseCase.startEarlySession( + schedule, + startedAt: _activePreparationRunStartedAt ?? _nowProvider(), + isCurrent: current, + ); + if (!current()) { + event.receipt?.complete(null); + return; + } + // The prompt remains owned through the DB failure boundary. The widget + // consumes that owner only after receiving this committed receipt. + _snapshotInvalidated = false; + _activeEarlyStartScheduleId = schedule.id; + _activePreparationRunStartedAt = result.startedAt; + _activePreparationActionEvents = result.actionEvents; + emit( + state.copyWith( + status: ScheduleStatus.started, + isEarlyStarted: true, + hasPendingStartRecovery: result.hasPendingRecovery, + ), + ); + _lastSnapshotSavedAt = result.startedAt; + _startPreparationTimer(); + event.receipt?.complete(result); + } catch (error, stack) { + if (event.receipt != null) { + if (current()) { + event.receipt!.completeError(error, stack); + } else { + event.receipt!.complete(null); + } + } else { + AppLogger.debug( + 'Preparation start failed errorType=${error.runtimeType}', + ); + } + } finally { + if (identical(_explicitStartPending, pending)) { + _explicitStartPending = null; + } + } + } - final startedAt = _nowProvider(); - await _schedulePreparationSessionUseCase.startEarlySession( - schedule, - startedAt: startedAt, - ); - + Future _onStartRecovery( + SchedulePreparationRecoveryRequested event, + Emitter emit, + ) async { + if (_finishing || + !state.hasPendingStartRecovery || + state.isRecoveringStart || + state.schedule == null || + _activePreparationRunStartedAt == null) { + return; + } + final valid = _captureBackgroundValidity(); + final id = state.schedule!.id; + final startedAt = _activePreparationRunStartedAt!; + bool current() => + valid() && + !emit.isDone && + !_finishing && + state.schedule?.id == id && + _activePreparationRunStartedAt == startedAt; if (!current()) return; - _snapshotInvalidated = false; - _activePreparationRunStartedAt = startedAt; - _activePreparationActionEvents = const []; - emit(ScheduleState.started(schedule, isEarlyStarted: true)); - _lastSnapshotSavedAt = startedAt; - _startPreparationTimer(); + emit(state.copyWith(isRecoveringStart: true)); + try { + final result = await _schedulePreparationSessionUseCase.startEarlySession( + state.schedule!, + startedAt: startedAt, + isCurrent: current, + ); + if (current()) { + emit( + state.copyWith( + hasPendingStartRecovery: result.hasPendingRecovery, + isRecoveringStart: false, + ), + ); + } + } catch (_) { + if (current()) emit(state.copyWith(isRecoveringStart: false)); + } } Future _onTick(ScheduleTick event, Emitter emit) => @@ -550,7 +664,11 @@ class ScheduleBloc extends Bloc { SchedulePreparationTimeRefreshRequested event, Emitter emit, ) async { - if (_notificationPromptOwner != null || state.schedule == null) return; + if (_finishing || + _notificationPromptOwner != null || + state.schedule == null) { + return; + } if (event.origin != PreparationRefreshOrigin.periodic) _stepRevision++; if (state.schedule!.preparation.isAllStepsDone) return; final startedAt = _activePreparationRunStartedAt; @@ -576,14 +694,21 @@ class ScheduleBloc extends Bloc { event.origin, observedAt, ); - await _saveTimedPreparationSnapshot(refreshedSchedule); + final saved = await _saveTimedPreparationSnapshot(refreshedSchedule); + if (!saved && !emit.isDone && state.schedule?.id == refreshedSchedule.id) { + emit(state.copyWith(hasPendingStartRecovery: true)); + } } Future _onStepSkipped( ScheduleStepSkipped event, Emitter emit, ) async { - if (_notificationPromptOwner != null || state.schedule == null) return; + if (_finishing || + _notificationPromptOwner != null || + state.schedule == null) { + return; + } if (state.schedule!.preparation.isAllStepsDone) return; _stepObservation = null; _stepRevision++; @@ -615,7 +740,10 @@ class ScheduleBloc extends Bloc { ); emit(state.copyWith(schedule: newSchedule)); _stepObservation = (_monotonicNow, now, _lifecycle); - await _saveTimedPreparationSnapshot(newSchedule, force: true); + final saved = await _saveTimedPreparationSnapshot(newSchedule, force: true); + if (!saved && !emit.isDone && state.schedule?.id == newSchedule.id) { + emit(state.copyWith(hasPendingStartRecovery: true)); + } } Future _onFinished( @@ -625,6 +753,8 @@ class ScheduleBloc extends Bloc { final current = _captureBackgroundValidity(); if (!current() || state.schedule == null) return; final scheduleId = state.schedule!.id; + _finishing = true; + _stopPreparationTimer(); _stepObservation = null; _stepRevision++; try { @@ -651,6 +781,12 @@ class ScheduleBloc extends Bloc { if (restoreNearest) add(const ScheduleSubscriptionRequested()); } catch (error) { AppLogger.debug('error finishing schedule: $error'); + if (current()) { + emit(state.copyWith(isRecoveringStart: false)); + _startPreparationTimer(); + } + } finally { + _finishing = false; } } @@ -821,23 +957,30 @@ class ScheduleBloc extends Bloc { return _schedulePreparationSessionUseCase.getEarlyStartSession(scheduleId); } - Future _saveTimedPreparationSnapshot( + Future _saveTimedPreparationSnapshot( ScheduleWithPreparationEntity schedule, { bool force = false, }) async { final now = _nowProvider(); if (!force && + !state.hasPendingStartRecovery && _lastSnapshotSavedAt != null && now.difference(_lastSnapshotSavedAt!) < const Duration(seconds: 5)) { - return; + return true; + } + try { + await _schedulePreparationSessionUseCase.saveTimedPreparationSnapshot( + schedule, + savedAt: now, + startedAt: _activePreparationRunStartedAt, + actionEvents: _activePreparationActionEvents, + persist: !state.hasPendingStartRecovery, + ); + _lastSnapshotSavedAt = now; + return !state.hasPendingStartRecovery; + } catch (_) { + return false; } - await _schedulePreparationSessionUseCase.saveTimedPreparationSnapshot( - schedule, - savedAt: now, - startedAt: _activePreparationRunStartedAt, - actionEvents: _activePreparationActionEvents, - ); - _lastSnapshotSavedAt = now; } Future _clearPersistedState(String scheduleId) async { diff --git a/lib/presentation/app/bloc/schedule/schedule_event.dart b/lib/presentation/app/bloc/schedule/schedule_event.dart index 446125b3..0240f284 100644 --- a/lib/presentation/app/bloc/schedule/schedule_event.dart +++ b/lib/presentation/app/bloc/schedule/schedule_event.dart @@ -52,7 +52,9 @@ final class ScheduleStarted extends ScheduleEvent { } final class SchedulePreparationStarted extends ScheduleEvent { - const SchedulePreparationStarted(); + const SchedulePreparationStarted({this.receipt, this.isCurrent}); + final Completer? receipt; + final bool Function()? isCurrent; @override List get props => []; @@ -101,3 +103,7 @@ final class _NotificationPromptPresented extends ScheduleEvent { @override List get props => [schedule, owner]; } + +final class SchedulePreparationRecoveryRequested extends ScheduleEvent { + const SchedulePreparationRecoveryRequested(); +} diff --git a/lib/presentation/app/bloc/schedule/schedule_state.dart b/lib/presentation/app/bloc/schedule/schedule_state.dart index f83b3b74..5d24eb4c 100644 --- a/lib/presentation/app/bloc/schedule/schedule_state.dart +++ b/lib/presentation/app/bloc/schedule/schedule_state.dart @@ -8,6 +8,8 @@ class ScheduleState extends Equatable { this.schedule, this.isEarlyStarted = false, this.notificationPromptOwner, + this.hasPendingStartRecovery = false, + this.isRecoveringStart = false, }); const ScheduleState.initial() : this._(status: ScheduleStatus.initial); @@ -39,17 +41,24 @@ class ScheduleState extends Equatable { final ScheduleWithPreparationEntity? schedule; final bool isEarlyStarted; final Object? notificationPromptOwner; + final bool hasPendingStartRecovery; + final bool isRecoveringStart; ScheduleState copyWith({ ScheduleStatus? status, ScheduleWithPreparationEntity? schedule, bool? isEarlyStarted, + bool? hasPendingStartRecovery, + bool? isRecoveringStart, }) { return ScheduleState._( status: status ?? this.status, schedule: schedule ?? this.schedule, isEarlyStarted: isEarlyStarted ?? this.isEarlyStarted, notificationPromptOwner: notificationPromptOwner, + hasPendingStartRecovery: + hasPendingStartRecovery ?? this.hasPendingStartRecovery, + isRecoveringStart: isRecoveringStart ?? this.isRecoveringStart, ); } @@ -71,5 +80,7 @@ class ScheduleState extends Equatable { schedule?.preparation, isEarlyStarted, notificationPromptOwner, + hasPendingStartRecovery, + isRecoveringStart, ]; } diff --git a/test/data/repositories/schedule_start_transaction_test.dart b/test/data/repositories/schedule_start_transaction_test.dart new file mode 100644 index 00000000..850d02da --- /dev/null +++ b/test/data/repositories/schedule_start_transaction_test.dart @@ -0,0 +1,244 @@ +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/domain/entities/early_start_session_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_action_event_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_with_time_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_step_with_time_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_with_preparation_entity.dart'; +import 'package:on_time_front/domain/entities/timed_preparation_snapshot_entity.dart'; +import 'package:on_time_front/domain/repositories/early_start_session_repository.dart'; +import 'package:on_time_front/domain/repositories/preparation_repository.dart'; +import 'package:on_time_front/domain/use-cases/schedule_preparation_session_use_case.dart'; +import 'package:on_time_front/domain/use-cases/cancel_schedule_alarm_use_case.dart'; +import '../../helpers/noop_alarm_reconciliation.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/data/repositories/schedule_repository_impl.dart'; +import 'package:on_time_front/domain/entities/place_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_entity.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; +import 'package:on_time_front/domain/repositories/timed_preparation_repository.dart'; + +void main() { + late AppDatabase db; + late ScheduleRepositoryImpl repository; + setUp(() async { + db = AppDatabase.forTesting(NativeDatabase.memory()); + repository = ScheduleRepositoryImpl( + database: db, + timedPreparationRepository: _Runtime(), + ); + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + isOnboardingCompleted: true, + ), + ); + await repository.createSchedule( + ScheduleEntity( + id: 'one', + place: const PlaceEntity(id: 'place', placeName: 'place'), + scheduleName: 'one', + scheduleTime: DateTime.utc(2026, 10), + moveTime: Duration.zero, + isChanged: false, + isStarted: false, + scheduleSpareTime: Duration.zero, + scheduleNote: '', + ), + ); + }); + tearDown(() async { + await repository.dispose(); + await db.close(); + }); + Future revision() async => + (await db.select(db.users).getSingle()).dataRevision; + + test( + 'a failed revision write rolls back the schedule transition and retry succeeds', + () async { + final before = await revision(); + await db.customStatement( + "CREATE TRIGGER reject_revision BEFORE UPDATE OF data_revision ON users BEGIN SELECT RAISE(ABORT, 'revision failed'); END", + ); + await expectLater(repository.startSchedule('one'), throwsA(anything)); + final failed = await repository.getScheduleById('one'); + expect(failed.isStarted, isFalse); + expect(failed.startedAt, isNull); + expect(failed.preparationFrozen, isFalse); + expect(await revision(), before); + await db.customStatement('DROP TRIGGER reject_revision'); + await repository.startSchedule('one'); + expect((await repository.getScheduleById('one')).isStarted, isTrue); + expect(await revision(), before + 1); + }, + ); + + test( + 'concurrent and repeated starts preserve first timestamp and one revision', + () async { + final before = await revision(); + await Future.wait([ + repository.startSchedule('one'), + repository.startSchedule('one'), + ]); + final first = await repository.getScheduleById('one'); + await repository.startSchedule('one'); + expect( + (await repository.getScheduleById('one')).startedAt, + first.startedAt, + ); + expect(await revision(), before + 1); + expect(first.preparationFrozen, isTrue); + }, + ); + + for (final (confirmation, missing) in [ + (false, false), + (true, false), + (false, true), + ]) { + test( + 'fresh session respects valid run vs explicit confirmation=$confirmation missing=$missing without overwriting durable first start', + () async { + final t0 = DateTime.utc(2026, 1, 1); + final t1 = t0.add(const Duration(hours: 1)); + final t2 = t1.add(const Duration(hours: 1)); + await repository.startSchedule('one', startedAt: t0); + final before = await revision(); + final schedule = + ScheduleWithPreparationEntity.fromScheduleAndPreparationEntity( + await repository.getScheduleById('one'), + const PreparationWithTimeEntity( + preparationStepList: [ + PreparationStepWithTimeEntity( + id: 'step', + preparationName: 'prep', + preparationTime: Duration(minutes: 10), + nextPreparationId: null, + ), + ], + ), + ); + final skip = PreparationActionEventEntity.skipStep( + stepId: 'step', + occurredAt: t1.add(const Duration(seconds: 1)), + ); + final runtime = _Runtime(); + if (!missing) { + runtime.snapshots['one'] = TimedPreparationSnapshotEntity( + preparation: schedule.preparation, + savedAt: t1, + scheduleFingerprint: schedule.cacheFingerprint, + startedAt: t1, + requiresConfirmation: confirmation, + actionEvents: [skip], + ); + } + final owner = AlarmOperationCoordinator(LocalDataOperationGate()); + final early = _Early(); + final session = SchedulePreparationSessionUseCase( + repository, + _Preparation(), + runtime, + early, + _Cancel(), + NoopAlarmReconciliation(), + operations: owner, + ); + addTearDown(session.dispose); + final receipt = await session.startEarlySession( + schedule, + startedAt: t2, + ); + expect(receipt.durableStartedAt!.toUtc(), t0); + expect(receipt.startedAt.toUtc(), (confirmation || missing) ? t2 : t1); + expect( + runtime.snapshots['one']!.startedAt!.toUtc(), + (confirmation || missing) ? t2 : t1, + ); + expect( + runtime.snapshots['one']!.actionEvents, + (confirmation || missing) ? isEmpty : [skip], + ); + expect( + (await repository.getScheduleById('one')).startedAt!.toUtc(), + t0, + ); + expect(await revision(), before); + owner.dispose(); + }, + ); + } + + test('completed and deleted occurrences cannot restart', () async { + await repository.finishSchedule('one', 0); + final before = await revision(); + await expectLater(repository.startSchedule('one'), throwsA(anything)); + expect( + (await repository.getScheduleById('one')).doneStatus, + ScheduleDoneStatus.normalEnd, + ); + expect(await revision(), before); + await repository.deleteSchedule(await repository.getScheduleById('one')); + await expectLater(repository.startSchedule('one'), throwsA(anything)); + }); +} + +class _Runtime implements TimedPreparationRepository { + final snapshots = {}; + @override + Future clearTimedPreparation(String id) async { + snapshots.remove(id); + } + + @override + Future getTimedPreparationSnapshot( + String id, + ) async => snapshots[id]; + @override + Future saveTimedPreparationSnapshot( + String id, + TimedPreparationSnapshotEntity value, + ) async { + snapshots[id] = value; + } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +class _Preparation implements PreparationRepository { + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +class _Cancel implements CancelScheduleAlarmUseCase { + @override + Future call(String id) async {} +} + +class _Early implements EarlyStartSessionRepository { + final sessions = {}; + @override + Future clear(String id) async { + sessions.remove(id); + } + + @override + Future getSession(String id) async => sessions[id]; + @override + Future markStarted({ + required String scheduleId, + required DateTime startedAt, + }) async { + sessions[scheduleId] = EarlyStartSessionEntity( + scheduleId: scheduleId, + startedAt: startedAt, + ); + } +} diff --git a/test/domain/use-cases/schedule_mutation_use_cases_test.dart b/test/domain/use-cases/schedule_mutation_use_cases_test.dart index c878ce75..1390b094 100644 --- a/test/domain/use-cases/schedule_mutation_use_cases_test.dart +++ b/test/domain/use-cases/schedule_mutation_use_cases_test.dart @@ -107,7 +107,6 @@ void main() { expect(scheduleRepository.startedScheduleIds, ['schedule-1']); }, ); - } ScheduleEntity _schedule(String id) { @@ -161,8 +160,12 @@ class _FakeScheduleRepository implements ScheduleRepository { } @override - Future startSchedule(String scheduleId) async { + Future startSchedule( + String scheduleId, { + DateTime? startedAt, + }) async { startedScheduleIds.add(scheduleId); + return startedAt ?? DateTime.utc(2026); } @override diff --git a/test/domain/use-cases/schedule_preparation_session_use_case_test.dart b/test/domain/use-cases/schedule_preparation_session_use_case_test.dart index 4b7449a6..fab93df5 100644 --- a/test/domain/use-cases/schedule_preparation_session_use_case_test.dart +++ b/test/domain/use-cases/schedule_preparation_session_use_case_test.dart @@ -1,3 +1,5 @@ +import 'package:on_time_front/domain/entities/preparation_action_event_entity.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; import 'package:rxdart/rxdart.dart'; import 'package:on_time_front/domain/entities/schedule_not_found.dart'; @@ -23,6 +25,265 @@ import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; import 'package:on_time_front/domain/use-cases/schedule_preparation_session_use_case.dart'; void main() { + test( + 'pending callers share failure, then retry the same instance and timestamp', + () async { + final schedules = _FakeScheduleRepository() + ..startBarrier = Completer() + ..failStarts = 1; + final early = _FakeEarlyStartSessionRepository(); + final owner = AlarmOperationCoordinator(LocalDataOperationGate()); + final useCase = SchedulePreparationSessionUseCase( + schedules, + _FakePreparationRepository(), + _FakeTimedPreparationRepository(), + early, + _FakeCancelScheduleAlarmUseCase(), + _FakeReconcileAlarmsUseCase(), + operations: owner, + ); + addTearDown(useCase.dispose); + final schedule = _scheduleWithPreparation('pending'); + final time = DateTime.utc(2026, 9, 1); + final first = useCase.startEarlySession(schedule, startedAt: time); + final second = useCase.startEarlySession( + schedule, + startedAt: time.add(const Duration(minutes: 1)), + ); + var completed = false; + final firstError = expectLater(first, throwsStateError); + final secondError = expectLater( + second, + throwsStateError, + ).then((_) => completed = true); + await Future.delayed(Duration.zero); + expect(completed, isFalse); + expect(early.sessions, isEmpty); + schedules.startBarrier!.complete(); + await Future.wait([firstError, secondError]); + final retried = await useCase.startEarlySession( + schedule, + startedAt: time.add(const Duration(minutes: 2)), + ); + expect(retried.startedAt, time); + expect(schedules.requestedTimes, [time, time]); + expect(early.sessions[schedule.id]!.startedAt, time); + owner.dispose(); + }, + ); + + test( + 'snapshot failure is partial and retry preserves a newer skip projection', + () async { + final schedules = _FakeScheduleRepository(); + final snapshots = _FakeTimedPreparationRepository()..failWrites = 1; + final owner = AlarmOperationCoordinator(LocalDataOperationGate()); + final useCase = SchedulePreparationSessionUseCase( + schedules, + _FakePreparationRepository(), + snapshots, + _FakeEarlyStartSessionRepository(), + _FakeCancelScheduleAlarmUseCase(), + _FakeReconcileAlarmsUseCase(), + operations: owner, + ); + addTearDown(useCase.dispose); + final schedule = _scheduleWithPreparation('partial'); + final time = DateTime.utc(2026); + final first = await useCase.startEarlySession(schedule, startedAt: time); + expect(first.hasPendingRecovery, isTrue); + final event = PreparationActionEventEntity.skipStep( + stepId: schedule.preparation.preparationStepList.first.id, + occurredAt: time.add(const Duration(seconds: 2)), + ); + await useCase.saveTimedPreparationSnapshot( + schedule, + startedAt: time, + actionEvents: [event], + ); + final retry = await useCase.startEarlySession( + schedule, + startedAt: time.add(const Duration(minutes: 2)), + ); + expect(retry.startedAt, time); + expect(retry.hasPendingRecovery, isFalse); + expect(schedules.startedScheduleIds, [schedule.id]); + expect(snapshots.snapshots[schedule.id]!.actionEvents, [event]); + owner.dispose(); + }, + ); + + test( + 'replacement generation does not reuse an old flight or write old runtime', + () async { + final gate = LocalDataOperationGate(); + final owner = AlarmOperationCoordinator(gate); + final schedules = _FakeScheduleRepository() + ..startBarrier = Completer(); + final early = _FakeEarlyStartSessionRepository(); + final useCase = SchedulePreparationSessionUseCase( + schedules, + _FakePreparationRepository(), + _FakeTimedPreparationRepository(), + early, + _FakeCancelScheduleAlarmUseCase(), + _FakeReconcileAlarmsUseCase(), + operations: owner, + ); + addTearDown(useCase.dispose); + final schedule = _scheduleWithPreparation('same-id'); + final old = useCase.startEarlySession( + schedule, + startedAt: DateTime.utc(2026), + ); + final oldFailure = expectLater( + old, + throwsA(isA()), + ); + await Future.delayed(Duration.zero); + await gate.run(() async {}, replacesData: true); + schedules.startBarrier!.complete(); + await oldFailure; + expect(early.sessions, isEmpty); + final freshTime = DateTime.utc(2027); + expect( + (await useCase.startEarlySession( + schedule, + startedAt: freshTime, + )).startedAt, + freshTime, + ); + expect(schedules.startedScheduleIds, [schedule.id, schedule.id]); + owner.dispose(); + }, + ); + + test( + 'marker failure keeps durable run and a bounded retry preserves its clock', + () async { + final schedules = _FakeScheduleRepository(); + final early = _FakeEarlyStartSessionRepository()..failWrites = 1; + final snapshots = _FakeTimedPreparationRepository(); + final owner = AlarmOperationCoordinator(LocalDataOperationGate()); + final session = SchedulePreparationSessionUseCase( + schedules, + _FakePreparationRepository(), + snapshots, + early, + _FakeCancelScheduleAlarmUseCase(), + _FakeReconcileAlarmsUseCase(), + operations: owner, + ); + addTearDown(session.dispose); + final schedule = _scheduleWithPreparation('marker'); + final first = await session.startEarlySession( + schedule, + startedAt: DateTime.utc(2026), + ); + expect(first.hasPendingRecovery, isTrue); + expect(snapshots.snapshots[schedule.id], isNotNull); + expect(early.sessions, isEmpty); + final retry = await session.startEarlySession( + schedule, + startedAt: DateTime.utc(2027), + ); + expect(retry.hasPendingRecovery, isFalse); + expect(retry.startedAt, first.startedAt); + expect(schedules.startedScheduleIds, [schedule.id]); + owner.dispose(); + }, + ); + + test( + 'finish while start cleanup is pending cannot resurrect the run', + () async { + final schedules = _FakeScheduleRepository(); + final snapshots = _FakeTimedPreparationRepository(); + final early = _FakeEarlyStartSessionRepository(); + final cancel = _FakeCancelScheduleAlarmUseCase() + ..barrier = Completer(); + final owner = AlarmOperationCoordinator(LocalDataOperationGate()); + final session = SchedulePreparationSessionUseCase( + schedules, + _FakePreparationRepository(), + snapshots, + early, + cancel, + _FakeReconcileAlarmsUseCase(), + operations: owner, + ); + addTearDown(session.dispose); + final schedule = _scheduleWithPreparation('finish'); + final start = session.startEarlySession( + schedule, + startedAt: DateTime.utc(2026), + ); + final stale = expectLater( + start, + throwsA(isA()), + ); + await cancel.entered.future; + final finish = session.finishSchedulePreparation( + schedule.id, + latenessTime: 0, + ); + await Future.delayed(Duration.zero); + cancel.barrier!.complete(); + await finish; + await stale; + expect(snapshots.snapshots, isEmpty); + expect(early.sessions, isEmpty); + expect(schedules.finishedSchedules, [(schedule.id, 0)]); + owner.dispose(); + }, + ); + + test( + 'dispose detaches the shared listener without releasing in-flight ownership', + () async { + final owner = AlarmOperationCoordinator(LocalDataOperationGate()); + final schedules = _FakeScheduleRepository() + ..startBarrier = Completer(); + final early = _FakeEarlyStartSessionRepository(); + final session = SchedulePreparationSessionUseCase( + schedules, + _FakePreparationRepository(), + _FakeTimedPreparationRepository(), + early, + _FakeCancelScheduleAlarmUseCase(), + _FakeReconcileAlarmsUseCase(), + operations: owner, + ); + final future = session.startEarlySession( + _scheduleWithPreparation('dispose'), + startedAt: DateTime.utc(2026), + ); + final failure = expectLater( + future, + throwsA(isA()), + ); + await Future.delayed(Duration.zero); + // Observe shared-owner subscription lifetime without adding a production debug API. + // ignore: invalid_use_of_protected_member + expect(owner.hasListeners, isTrue); + session.dispose(); + // Observe shared-owner subscription lifetime without adding a production debug API. + // ignore: invalid_use_of_protected_member + expect(owner.hasListeners, isFalse); + var released = false; + final following = owner.cleanup(() async { + released = true; + }); + await Future.delayed(Duration.zero); + expect(released, isFalse); + schedules.startBarrier!.complete(); + await failure; + await following; + expect(early.sessions, isEmpty); + owner.dispose(); + }, + ); + test( 'early start begins the schedule preparation session once and saves progress', () async { @@ -38,6 +299,7 @@ void main() { cancelScheduleAlarmUseCase, _FakeReconcileAlarmsUseCase(), ); + addTearDown(useCase.dispose); final schedule = _scheduleWithPreparation('schedule-1'); final startedAt = DateTime.utc(2026, 6, 28, 8); @@ -48,7 +310,7 @@ void main() { earlyStartSessionRepository.sessions[schedule.id], EarlyStartSessionEntity(scheduleId: schedule.id, startedAt: startedAt), ); - expect(scheduleRepository.startedScheduleIds, [schedule.id]); + expect(scheduleRepository.startedScheduleIds, [schedule.id, schedule.id]); expect(cancelScheduleAlarmUseCase.cancelledScheduleIds, [schedule.id]); final snapshot = timedPreparationRepository.snapshots[schedule.id]!; @@ -73,6 +335,7 @@ void main() { _FakeCancelScheduleAlarmUseCase()..fails = true, reconcile, ); + addTearDown(useCase.dispose); final schedule = _scheduleWithPreparation('schedule-1'); await useCase.startEarlySession(schedule, startedAt: DateTime.utc(2026)); expect(schedules.startedScheduleIds, [schedule.id]); @@ -96,6 +359,7 @@ void main() { _FakeCancelScheduleAlarmUseCase()..fails = true, reconcile, ); + addTearDown(useCase.dispose); await useCase.finishSchedulePreparation('schedule-1', latenessTime: 7); expect(schedules.finishedSchedules, [('schedule-1', 7)]); expect(snapshots.clearedScheduleIds, ['schedule-1']); @@ -116,6 +380,7 @@ void main() { _FakeCancelScheduleAlarmUseCase(), _FakeReconcileAlarmsUseCase(), ); + addTearDown(useCase.dispose); final schedule = _scheduleWithPreparation('schedule-1'); final now = DateTime.utc(2026, 6, 28, 8, 10); final savedPreparation = schedule.preparation.timeElapsed( @@ -154,6 +419,7 @@ void main() { _FakeCancelScheduleAlarmUseCase(), _FakeReconcileAlarmsUseCase(), ); + addTearDown(useCase.dispose); final schedule = _scheduleWithPreparation('schedule-1'); final startedAt = DateTime.utc(2026, 6, 28, 7, 50); earlyStartSessionRepository.sessions[schedule.id] = @@ -195,6 +461,7 @@ void main() { cancelScheduleAlarmUseCase, _FakeReconcileAlarmsUseCase(), ); + addTearDown(useCase.dispose); scheduleRepository.schedulesById['schedule-1'] = _scheduleEntity( 'schedule-1', doneStatus: ScheduleDoneStatus.normalEnd, @@ -226,6 +493,7 @@ void main() { cancelScheduleAlarmUseCase, _FakeReconcileAlarmsUseCase(), ); + addTearDown(useCase.dispose); final schedule = _scheduleEntity('schedule-1'); final preparation = _preparation('prep-1'); final expectedSchedule = @@ -261,6 +529,7 @@ void main() { cancelScheduleAlarmUseCase, _FakeReconcileAlarmsUseCase(), ); + addTearDown(useCase.dispose); final readyPrompt = await useCase.resolvePromptedSchedule( scheduleId: 'ready-prompt', @@ -293,6 +562,7 @@ void main() { _FakeCancelScheduleAlarmUseCase(), _FakeReconcileAlarmsUseCase(), ); + addTearDown(useCase.dispose); var result = await useCase.resolvePromptedSchedule( scheduleId: 'recurring-occurrence', startPreparation: false, @@ -328,6 +598,7 @@ void main() { _FakeCancelScheduleAlarmUseCase(), _FakeReconcileAlarmsUseCase(), ); + addTearDown(useCase.dispose); final result = await useCase.resolvePromptedSchedule( scheduleId: 'missing', startPreparation: false, @@ -353,6 +624,7 @@ void main() { cancelScheduleAlarmUseCase, reconcileAlarmsUseCase, ); + addTearDown(useCase.dispose); final schedule = _scheduleWithPreparation('schedule-1'); timedPreparationRepository.snapshots[schedule.id] = TimedPreparationSnapshotEntity( @@ -444,9 +716,19 @@ class _FakeScheduleRepository implements ScheduleRepository { DateTime endDate, ) => const Stream.empty(); + int failStarts = 0; + Completer? startBarrier; + final requestedTimes = []; @override - Future startSchedule(String scheduleId) async { + Future startSchedule( + String scheduleId, { + DateTime? startedAt, + }) async { startedScheduleIds.add(scheduleId); + requestedTimes.add(startedAt); + await startBarrier?.future; + if (failStarts-- > 0) throw StateError('start write failed'); + return startedAt ?? DateTime.utc(2026); } @override @@ -493,12 +775,14 @@ class _FakePreparationRepository implements PreparationRepository { class _FakeTimedPreparationRepository implements TimedPreparationRepository { final snapshots = {}; final clearedScheduleIds = []; + int failWrites = 0; @override Future saveTimedPreparationSnapshot( String scheduleId, TimedPreparationSnapshotEntity snapshot, ) async { + if (failWrites-- > 0) throw StateError('snapshot write failed'); snapshots[scheduleId] = snapshot; } @@ -517,6 +801,7 @@ class _FakeTimedPreparationRepository implements TimedPreparationRepository { } class _FakeEarlyStartSessionRepository implements EarlyStartSessionRepository { + int failWrites = 0; final sessions = {}; final clearedScheduleIds = []; @@ -525,6 +810,7 @@ class _FakeEarlyStartSessionRepository implements EarlyStartSessionRepository { required String scheduleId, required DateTime startedAt, }) async { + if (failWrites-- > 0) throw StateError('marker unavailable'); sessions[scheduleId] = EarlyStartSessionEntity( scheduleId: scheduleId, startedAt: startedAt, @@ -546,10 +832,14 @@ class _FakeEarlyStartSessionRepository implements EarlyStartSessionRepository { class _FakeCancelScheduleAlarmUseCase implements CancelScheduleAlarmUseCase { final cancelledScheduleIds = []; bool fails = false; + Completer? barrier; + final entered = Completer(); @override Future call(String scheduleId) async { cancelledScheduleIds.add(scheduleId); + if (!entered.isCompleted) entered.complete(); + await barrier?.future; if (fails) throw const AlarmCleanupIncomplete(); } } diff --git a/test/domain/use-cases/timed_preparation_use_cases_test.dart b/test/domain/use-cases/timed_preparation_use_cases_test.dart index 4ed75b66..61f832d4 100644 --- a/test/domain/use-cases/timed_preparation_use_cases_test.dart +++ b/test/domain/use-cases/timed_preparation_use_cases_test.dart @@ -384,7 +384,10 @@ class _FakeScheduleRepository implements ScheduleRepository { Future finishSchedule(String scheduleId, int latenessTime) async {} @override - Future startSchedule(String scheduleId) async {} + Future startSchedule( + String scheduleId, { + DateTime? startedAt, + }) async => startedAt ?? DateTime.utc(2026); @override Future getScheduleById(String id) async => diff --git a/test/presentation/alarm/screens/preparation_flow_widget_test.dart b/test/presentation/alarm/screens/preparation_flow_widget_test.dart index 1e72324e..c5fb19af 100644 --- a/test/presentation/alarm/screens/preparation_flow_widget_test.dart +++ b/test/presentation/alarm/screens/preparation_flow_widget_test.dart @@ -170,6 +170,9 @@ EarlyStartUseCaseBundle createEarlyStartUseCaseBundle() { class TestSchedulePreparationSessionUseCase implements SchedulePreparationSessionUseCase { + @override + void dispose() {} + TestSchedulePreparationSessionUseCase({ required this.saveTimedPreparationUseCase, required this.getTimedPreparationSnapshotUseCase, @@ -192,9 +195,10 @@ class TestSchedulePreparationSessionUseCase final _startedScheduleIds = {}; @override - Future startEarlySession( + Future startEarlySession( ScheduleWithPreparationEntity schedule, { required DateTime startedAt, + bool Function()? isCurrent, }) async { await markEarlyStartSessionUseCase( scheduleId: schedule.id, @@ -206,6 +210,7 @@ class TestSchedulePreparationSessionUseCase savedAt: startedAt, startedAt: startedAt, ); + return PreparationStartReceipt(startedAt: startedAt); } @override @@ -230,6 +235,7 @@ class TestSchedulePreparationSessionUseCase DateTime? savedAt, DateTime? startedAt, List actionEvents = const [], + bool persist = true, }) { return saveTimedPreparationUseCase( schedule, @@ -597,6 +603,26 @@ void main() { (tester) async { await setLargeTestViewport(tester); + await tester.runAsync(() async { + bloc.add( + ScheduleUpcomingReceived( + buildSchedule( + id: 'prompt', + scheduleTime: now.add(const Duration(hours: 2)), + steps: const [ + PreparationStepWithTimeEntity( + id: 'step', + preparationName: 'prep', + preparationTime: Duration(minutes: 10), + nextPreparationId: null, + ), + ], + ), + ), + ); + await bloc.stream.firstWhere((state) => state.schedule != null); + }); + final router = GoRouter( initialLocation: '/scheduleStart', routes: [ @@ -622,7 +648,13 @@ void main() { expect(find.text('Start preparing now'), findsNothing); expect(find.byType(ElevatedButton), findsOneWidget); + final committed = bloc.stream.firstWhere( + (state) => state.status == ScheduleStatus.started, + ); await tapAndPump(tester, find.text('Start Preparing')); + await tester.runAsync( + () => committed.timeout(const Duration(seconds: 5)), + ); await pumpUntilRouteText(tester, 'ALARM_ROUTE'); expect(find.text('ALARM_ROUTE'), findsOneWidget); @@ -742,6 +774,26 @@ void main() { (tester) async { await setLargeTestViewport(tester); + await tester.runAsync(() async { + bloc.add( + ScheduleUpcomingReceived( + buildSchedule( + id: 'prompt', + scheduleTime: now.add(const Duration(hours: 2)), + steps: const [ + PreparationStepWithTimeEntity( + id: 'step', + preparationName: 'prep', + preparationTime: Duration(minutes: 10), + nextPreparationId: null, + ), + ], + ), + ), + ); + await bloc.stream.firstWhere((state) => state.schedule != null); + }); + final router = GoRouter( initialLocation: '/scheduleStart', routes: [ @@ -763,7 +815,13 @@ void main() { ); await pumpWithRouter(tester, bloc: bloc, router: router); + final committed = bloc.stream.firstWhere( + (state) => state.status == ScheduleStatus.started, + ); await tapAndPump(tester, find.text('Start preparing now')); + await tester.runAsync( + () => committed.timeout(const Duration(seconds: 5)), + ); await pumpUntilRouteText(tester, 'ALARM_ROUTE'); expect(find.text('ALARM_ROUTE'), findsOneWidget); }, @@ -972,7 +1030,10 @@ void main() { ), ], ); - bloc.add(ScheduleUpcomingReceived(schedule)); + await tester.runAsync(() async { + bloc.add(ScheduleUpcomingReceived(schedule)); + await bloc.stream.firstWhere((state) => state.schedule != null); + }); final router = GoRouter( initialLocation: '/scheduleStart', @@ -995,7 +1056,13 @@ void main() { ); await pumpWithRouter(tester, bloc: bloc, router: router); + final committed = bloc.stream.firstWhere( + (state) => state.status == ScheduleStatus.started, + ); await tapAndPump(tester, find.text('Start preparing now')); + await tester.runAsync( + () => committed.timeout(const Duration(seconds: 5)), + ); await pumpUntilRouteText(tester, 'ALARM_ROUTE'); expect(find.text('ALARM_ROUTE'), findsOneWidget); diff --git a/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart b/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart new file mode 100644 index 00000000..f82f2eea --- /dev/null +++ b/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart @@ -0,0 +1,292 @@ +import 'dart:async'; +import 'package:drift/native.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/services/alarm_operation_coordinator.dart'; +import 'package:on_time_front/data/repositories/schedule_repository_impl.dart'; +import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/domain/entities/early_start_session_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_step_with_time_entity.dart'; +import 'package:on_time_front/domain/entities/timed_preparation_snapshot_entity.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; +import 'package:on_time_front/domain/repositories/early_start_session_repository.dart'; +import 'package:on_time_front/domain/repositories/preparation_repository.dart'; +import 'package:on_time_front/domain/repositories/timed_preparation_repository.dart'; +import 'package:on_time_front/domain/use-cases/cancel_schedule_alarm_use_case.dart'; +import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; +import 'package:on_time_front/domain/use-cases/schedule_preparation_session_use_case.dart'; +import 'package:on_time_front/presentation/alarm/screens/schedule_start_screen.dart'; +import 'package:on_time_front/presentation/app/bloc/schedule/schedule_bloc.dart'; +import 'preparation_flow_widget_test.dart' + show + buildSchedule, + pumpWithRouter, + StubGetNearestUpcomingScheduleUseCase, + SpyNavigationService; + +void main() { + for (final leave in [false, true]) { + testWidgets( + 'real button awaits durable commit; failure retries and stale route=$leave never navigates', + (tester) async { + tester.view.physicalSize = const Size(430, 932); + tester.view.devicePixelRatio = 1; + addTearDown(() { + tester.view.resetPhysicalSize(); + tester.view.resetDevicePixelRatio(); + }); + final db = (await tester.runAsync( + () async => AppDatabase.forTesting(NativeDatabase.memory()), + ))!; + final snapshots = _Snapshots(); + final repository = (await tester.runAsync( + () async => ScheduleRepositoryImpl( + database: db, + timedPreparationRepository: snapshots, + ), + ))!; + final owner = AlarmOperationCoordinator(LocalDataOperationGate()); + final cancel = _Cancel(); + final session = _ObservedSession(repository, snapshots, cancel, owner); + addTearDown(session.dispose); + final now = DateTime.utc(2026, 10, 1); + final schedule = buildSchedule( + id: 'button', + scheduleTime: now.add(const Duration(hours: 2)), + steps: const [ + PreparationStepWithTimeEntity( + id: 'one', + preparationName: 'one', + preparationTime: Duration(minutes: 10), + nextPreparationId: 'two', + ), + PreparationStepWithTimeEntity( + id: 'two', + preparationName: 'two', + preparationTime: Duration(minutes: 10), + nextPreparationId: null, + ), + ], + ); + await tester.runAsync(() async { + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + isOnboardingCompleted: true, + ), + ); + await repository.createSchedule(schedule); + await db.customStatement( + "CREATE TRIGGER reject_start BEFORE UPDATE OF is_started ON schedules BEGIN SELECT RAISE(ABORT, 'disk write failed'); END", + ); + }); + final bloc = ScheduleBloc.test( + StubGetNearestUpcomingScheduleUseCase(() => const Stream.empty()), + SpyNavigationService(), + session, + nowProvider: () => now, + ); + final promptOwner = Object(); + bloc.presentNotificationPrompt(schedule, promptOwner, () => true); + var consumed = 0; + final router = GoRouter( + initialLocation: '/start', + routes: [ + GoRoute( + path: '/start', + builder: (_, __) => ScheduleStartScreen( + requiresExplicitStart: true, + onExplicitStart: () { + consumed++; + bloc.confirmNotificationPrompt(promptOwner); + }, + ), + ), + GoRoute( + path: '/alarmScreen', + builder: (_, __) => const Scaffold(body: Text('COMMITTED')), + ), + GoRoute( + path: '/home', + builder: (_, __) => const Scaffold(body: Text('HOME')), + ), + ], + ); + addTearDown(() async { + if (cancel.barrier != null && !cancel.barrier!.isCompleted) { + cancel.barrier!.complete(); + } + await bloc.close(); + router.dispose(); + await repository.dispose(); + await db.close(); + owner.dispose(); + }); + await pumpWithRouter(tester, bloc: bloc, router: router); + await tester.tap(find.text('Start Preparing')); + await tester.pump(); + await tester.runAsync( + () => session.completed.future.timeout(const Duration(seconds: 5)), + ); + await tester.pump(); + expect( + find.byKey(const Key('preparation-start-error')), + findsOneWidget, + ); + expect(find.text('COMMITTED'), findsNothing); + expect(consumed, 0); + expect(bloc.ownsNotificationPrompt(promptOwner), isTrue); + await tester.runAsync(() async { + expect( + (await repository.getScheduleById(schedule.id)).isStarted, + isFalse, + ); + await db.customStatement('DROP TRIGGER reject_start'); + }); + cancel.barrier = Completer(); + await tester.tap(find.text('Start Preparing')); + await tester.pump(); + await tester.runAsync( + () => cancel.entered.future.timeout(const Duration(seconds: 5)), + ); + await tester.pump(); + expect(find.byType(LinearProgressIndicator), findsOneWidget); + expect( + tester.widget(find.byType(ElevatedButton)).onPressed, + isNull, + ); + expect(consumed, 0); + if (leave) { + router.go('/home'); + await tester.pumpAndSettle(); + } + cancel.barrier!.complete(); + await tester.runAsync( + () => session.completed.future.timeout(const Duration(seconds: 5)), + ); + await tester.pumpAndSettle(); + expect(find.text(leave ? 'HOME' : 'COMMITTED'), findsOneWidget); + expect(consumed, leave ? 0 : 1); + await tester.runAsync(() async { + final row = await repository.getScheduleById(schedule.id); + expect(row.isStarted, isTrue); + expect(row.preparationFrozen, isTrue); + expect(row.startedAt!.toUtc(), now); + }); + await tester.runAsync(() => bloc.close()); + }, + ); + } +} + +class _Preparation implements PreparationRepository { + @override + dynamic noSuchMethod(Invocation i) => super.noSuchMethod(i); +} + +class _Snapshots implements TimedPreparationRepository { + final snapshots = {}; + @override + Future clearTimedPreparation(String id) async { + snapshots.remove(id); + } + + @override + Future getTimedPreparationSnapshot( + String id, + ) async => snapshots[id]; + @override + Future saveTimedPreparationSnapshot( + String id, + TimedPreparationSnapshotEntity value, + ) async { + snapshots[id] = value; + } +} + +class _Early implements EarlyStartSessionRepository { + final sessions = {}; + @override + Future clear(String id) async { + sessions.remove(id); + } + + @override + Future getSession(String id) async => sessions[id]; + @override + Future markStarted({ + required String scheduleId, + required DateTime startedAt, + }) async { + sessions[scheduleId] = EarlyStartSessionEntity( + scheduleId: scheduleId, + startedAt: startedAt, + ); + } +} + +class _Cancel implements CancelScheduleAlarmUseCase { + Completer? barrier; + final entered = Completer(); + @override + Future call(String id) async { + if (!entered.isCompleted) entered.complete(); + await barrier?.future; + } +} + +class _Reconcile implements ReconcileAlarmsUseCase { + @override + Future call() async => AlarmReconciliationResult( + status: AlarmReconciliationStatus.armed, + nativeAlarmProvider: AlarmProvider.none, + fallbackProvider: AlarmProvider.localNotification, + armedScheduleIds: [], + skippedScheduleCount: 0, + failures: [], + scheduleWindowStart: DateTime.utc(2026), + scheduleWindowEnd: DateTime.utc(2026), + alarmCoverageStart: DateTime.utc(2026), + alarmCoverageEnd: DateTime.utc(2026), + ); +} + +class _ObservedSession extends SchedulePreparationSessionUseCase { + _ObservedSession( + ScheduleRepositoryImpl repository, + _Snapshots snapshots, + _Cancel cancel, + AlarmOperationCoordinator owner, + ) : super( + repository, + _Preparation(), + snapshots, + _Early(), + cancel, + _Reconcile(), + operations: owner, + ); + Completer completed = Completer(); + @override + Future startEarlySession( + schedule, { + required DateTime startedAt, + bool Function()? isCurrent, + }) async { + completed = Completer(); + try { + return await super.startEarlySession( + schedule, + startedAt: startedAt, + isCurrent: isCurrent, + ); + } finally { + completed.complete(); + } + } +} diff --git a/test/presentation/app/bloc/schedule/preparation_start_recovery_test.dart b/test/presentation/app/bloc/schedule/preparation_start_recovery_test.dart new file mode 100644 index 00000000..dffd1ff2 --- /dev/null +++ b/test/presentation/app/bloc/schedule/preparation_start_recovery_test.dart @@ -0,0 +1,123 @@ +import 'dart:async'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/domain/entities/preparation_action_event_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_step_with_time_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_with_preparation_entity.dart'; +import 'package:on_time_front/domain/use-cases/schedule_preparation_session_use_case.dart'; +import 'package:on_time_front/presentation/app/bloc/schedule/schedule_bloc.dart'; +import '../../../alarm/screens/preparation_flow_widget_test.dart' + show + buildSchedule, + StubGetNearestUpcomingScheduleUseCase, + SpyNavigationService; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + test( + 'partial start survives same-run emissions; timer and skip retain memory until one explicit retry', + () async { + final now = DateTime.utc(2026, 10); + final schedule = buildSchedule( + id: 'partial', + scheduleTime: now.add(const Duration(hours: 2)), + steps: const [ + PreparationStepWithTimeEntity( + id: 'one', + preparationName: 'one', + preparationTime: Duration(minutes: 10), + nextPreparationId: 'two', + ), + PreparationStepWithTimeEntity( + id: 'two', + preparationName: 'two', + preparationTime: Duration(minutes: 10), + nextPreparationId: null, + ), + ], + ); + final session = _Session(); + final bloc = ScheduleBloc.test( + StubGetNearestUpcomingScheduleUseCase(() => const Stream.empty()), + SpyNavigationService(), + session, + nowProvider: () => now, + ); + addTearDown(bloc.close); + final prompt = Object(); + bloc.presentNotificationPrompt(schedule, prompt, () => true); + await Future.delayed(Duration.zero); + await bloc.requestPreparationStart(isCurrent: () => true); + bloc.confirmNotificationPrompt(prompt); + // Release selected-view ownership so a genuine repository emission reaches + // the same-run pending recovery guard. + final owner = bloc.notificationPreparationOwner!; + final view = Object(); + bloc.attachNotificationPreparation(owner, view); + bloc.releaseNotificationPreparation(owner, view); + await Future.delayed(Duration.zero); + expect(bloc.state.hasPendingStartRecovery, isTrue); + bloc.add(ScheduleUpcomingReceived(schedule)); + bloc.add(const ScheduleStepSkipped()); + await Future.delayed(Duration.zero); + expect(bloc.state.hasPendingStartRecovery, isTrue); + expect(session.persistAttempts, 0); + expect(session.events.single.stepId, 'one'); + expect(session.starts, 1); + session.barrier = Completer(); + bloc.add(const SchedulePreparationRecoveryRequested()); + await Future.delayed(Duration.zero); + expect(bloc.state.isRecoveringStart, isTrue); + bloc.add(const SchedulePreparationRecoveryRequested()); + await Future.delayed(Duration.zero); + expect(session.starts, 2); + session.barrier!.completeError(StateError('still unavailable')); + await Future.delayed(Duration.zero); + expect(bloc.state.isRecoveringStart, isFalse); + expect(bloc.state.hasPendingStartRecovery, isTrue); + session.barrier = null; + session.partial = false; + bloc.add(const SchedulePreparationRecoveryRequested()); + await Future.delayed(Duration.zero); + expect(bloc.state.hasPendingStartRecovery, isFalse); + expect(session.starts, 3); + expect(session.events.single.stepId, 'one'); + }, + ); +} + +class _Session implements SchedulePreparationSessionUseCase { + bool partial = true; + int starts = 0; + int persistAttempts = 0; + Completer? barrier; + List events = []; + @override + Future startEarlySession( + ScheduleWithPreparationEntity schedule, { + required DateTime startedAt, + bool Function()? isCurrent, + }) async { + starts++; + await barrier?.future; + return PreparationStartReceipt( + startedAt: startedAt, + hasPendingRecovery: partial, + actionEvents: events, + ); + } + + @override + Future saveTimedPreparationSnapshot( + ScheduleWithPreparationEntity schedule, { + DateTime? savedAt, + DateTime? startedAt, + List actionEvents = const [], + bool persist = true, + }) async { + events = actionEvents; + if (persist) persistAttempts++; + } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} diff --git a/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart b/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart index 8e547ba7..7888289d 100644 --- a/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart +++ b/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart @@ -35,6 +35,7 @@ class _Session implements SchedulePreparationSessionUseCase { DateTime? savedAt, DateTime? startedAt, List actionEvents = const [], + bool persist = true, }) async {} @override Future clearPersistedState(String id) async {} diff --git a/test/presentation/app/bloc/schedule/schedule_bloc_test.dart b/test/presentation/app/bloc/schedule/schedule_bloc_test.dart index 13419f92..247a82bb 100644 --- a/test/presentation/app/bloc/schedule/schedule_bloc_test.dart +++ b/test/presentation/app/bloc/schedule/schedule_bloc_test.dart @@ -205,6 +205,9 @@ class SpyClearEarlyStartSessionUseCase class FakeSchedulePreparationSessionUseCase implements SchedulePreparationSessionUseCase { + @override + void dispose() {} + FakeSchedulePreparationSessionUseCase({ required this.saveTimedPreparationUseCase, required this.getTimedPreparationSnapshotUseCase, @@ -238,9 +241,10 @@ class FakeSchedulePreparationSessionUseCase final _startedScheduleIds = {}; @override - Future startEarlySession( + Future startEarlySession( ScheduleWithPreparationEntity schedule, { required DateTime startedAt, + bool Function()? isCurrent, }) async { await markEarlyStartSessionUseCase( scheduleId: schedule.id, @@ -254,6 +258,7 @@ class FakeSchedulePreparationSessionUseCase startedAt: startedAt, actionEvents: const [], ); + return PreparationStartReceipt(startedAt: startedAt); } @override @@ -278,6 +283,7 @@ class FakeSchedulePreparationSessionUseCase DateTime? savedAt, DateTime? startedAt, List actionEvents = const [], + bool persist = true, }) { return saveTimedPreparationUseCase( schedule, diff --git a/test/presentation/shared/router/notification_launch_widget_test.dart b/test/presentation/shared/router/notification_launch_widget_test.dart index d5b7e6cd..186232d5 100644 --- a/test/presentation/shared/router/notification_launch_widget_test.dart +++ b/test/presentation/shared/router/notification_launch_widget_test.dart @@ -311,11 +311,13 @@ class _Session implements SchedulePreparationSessionUseCase { final startedIds = []; int get starts => startedIds.length; @override - Future startEarlySession( + Future startEarlySession( ScheduleWithPreparationEntity schedule, { required DateTime startedAt, + bool Function()? isCurrent, }) async { startedIds.add(schedule.id); + return PreparationStartReceipt(startedAt: startedAt); } @override @@ -336,6 +338,7 @@ class _Session implements SchedulePreparationSessionUseCase { DateTime? savedAt, DateTime? startedAt, List actionEvents = const [], + bool persist = true, }) async {} @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); diff --git a/tool/qa/capture_preparation_start.dart b/tool/qa/capture_preparation_start.dart new file mode 100644 index 00000000..fa98f1a8 --- /dev/null +++ b/tool/qa/capture_preparation_start.dart @@ -0,0 +1,144 @@ +// Widget raster evidence for A07 receipts; this does not exercise a mobile OS. +import 'dart:io'; +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/domain/entities/preparation_step_with_time_entity.dart'; +import 'package:on_time_front/domain/use-cases/schedule_preparation_session_use_case.dart'; +import 'package:on_time_front/l10n/app_localizations.dart'; +import 'package:on_time_front/presentation/alarm/screens/alarm_screen.dart'; +import 'package:on_time_front/presentation/alarm/screens/schedule_start_screen.dart'; +import 'package:on_time_front/presentation/app/bloc/schedule/schedule_bloc.dart'; +import 'package:on_time_front/presentation/shared/theme/theme.dart'; +import '../../test/presentation/alarm/screens/preparation_flow_widget_test.dart' + show StaticScheduleBloc, buildSchedule; + +void main() { + testWidgets('capture preparation start error and recovery at 200 percent', ( + tester, + ) async { + final output = Directory(Platform.environment['A07_CAPTURE_DIR']!); + await tester.runAsync(() => output.create(recursive: true)); + final font = FontLoader('Pretendard') + ..addFont(rootBundle.load('assets/fonts/Pretendard-Regular.ttf')); + await tester.runAsync(font.load); + final icons = FontLoader('MaterialIcons') + ..addFont(rootBundle.load('fonts/MaterialIcons-Regular.otf')); + await tester.runAsync(icons.load); + tester.view.physicalSize = const Size(430, 932); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + final now = DateTime.utc(2026, 10); + for (final locale in ['ko', 'en']) { + for (final partial in [false, true]) { + final schedule = buildSchedule( + id: 'capture', + scheduleTime: now.add(const Duration(hours: 2)), + steps: [ + PreparationStepWithTimeEntity( + id: 'one', + preparationName: locale == 'ko' ? '준비하기' : 'Get ready', + preparationTime: const Duration(minutes: 10), + nextPreparationId: 'two', + ), + PreparationStepWithTimeEntity( + id: 'two', + preparationName: locale == 'ko' ? '다음 단계' : 'Next step', + preparationTime: const Duration(minutes: 5), + nextPreparationId: null, + ), + ], + ); + final bloc = _CaptureBloc( + partial + ? ScheduleState.started( + schedule, + isEarlyStarted: true, + ).copyWith(hasPendingStartRecovery: true) + : ScheduleState.upcoming(schedule), + ); + final key = GlobalKey(); + await tester.pumpWidget( + BlocProvider.value( + value: bloc, + child: MaterialApp( + key: ValueKey('$locale-$partial'), + theme: themeData, + locale: Locale(locale), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: MediaQuery( + data: const MediaQueryData( + size: Size(430, 932), + textScaler: TextScaler.linear(2), + ), + child: RepaintBoundary( + key: key, + child: partial + ? AlarmScreen(nowProvider: () => now) + : const ScheduleStartScreen(), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + if (!partial) { + await tester.tap(find.byType(ElevatedButton).first); + await tester.pumpAndSettle(); + expect( + find.byKey(const Key('preparation-start-error')), + findsOneWidget, + ); + } + expect(tester.takeException(), isNull); + final boundary = + key.currentContext!.findRenderObject()! as RenderRepaintBoundary; + await tester.runAsync(() async { + final image = await boundary.toImage(pixelRatio: 2); + final bytes = await image.toByteData(format: ui.ImageByteFormat.png); + await File( + '${output.path}/preparation-${partial ? 'partial' : 'error'}-$locale.png', + ).writeAsBytes(bytes!.buffer.asUint8List(), flush: true); + image.dispose(); + }); + if (partial) { + final skip = find.text('이 단계 건너 뛰기'); + await tester.scrollUntilVisible( + skip, + 50, + scrollable: find.byType(Scrollable).first, + ); + await tester.pumpAndSettle(); + expect(skip.hitTestable(), findsOneWidget); + await tester.tap(skip); + expect( + bloc.addedEvents.whereType(), + hasLength(1), + ); + final next = find.text(locale == 'ko' ? '다음 단계' : 'Next step'); + await tester.scrollUntilVisible( + next, + 50, + scrollable: find.byType(Scrollable).first, + ); + await tester.pumpAndSettle(); + expect(next.hitTestable(), findsOneWidget); + expect(tester.takeException(), isNull); + } + } + } + }); +} + +class _CaptureBloc extends StaticScheduleBloc { + _CaptureBloc(super.state); + @override + Future requestPreparationStart({ + required bool Function() isCurrent, + }) async => throw StateError('simulated durable write failure'); +} From 28baf6c7b91c00d2c6490f597375c1dad39fc2b6 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:33:14 +0900 Subject: [PATCH 43/59] docs: record A07 verification and native QA contracts --- handoff/audit-2026-09-23-progress.md | 8 + plans/audit-2026-09-23/README.md | 6 +- .../a07-final-validation.json | 119 +++ .../artifacts/a07/a07-analyze.log | 2 + .../artifacts/a07/a07-coverage.log | 1 + .../artifacts/a07/a07-final-focused.log | 58 ++ .../artifacts/a07/a07-full.log | 950 ++++++++++++++++++ .../artifacts/a07/a07-generate.log | 35 + .../artifacts/a07/a07-generated-policy.log | 1 + .../artifacts/a07/a07-local-only-policy.log | 1 + .../artifacts/a07/a07-manifest-policy.log | 1 + .../artifacts/a07/a07-negative.log | 38 + .../artifacts/a07/a07-python-policy.log | 16 + .../artifacts/a07/a07-review-transcript.md | 63 ++ .../artifacts/a07/a07-ui-capture.log | 3 + .../artifacts/a07/a07-ui-overflow-before.log | 94 ++ .../artifacts/a07/preparation-error-en.png | Bin 0 -> 148794 bytes .../artifacts/a07/preparation-error-ko.png | Bin 0 -> 129477 bytes .../artifacts/a07/preparation-partial-en.png | Bin 0 -> 120949 bytes .../artifacts/a07/preparation-partial-ko.png | Bin 0 -> 102028 bytes .../artifacts/a07/source-freeze.json | 35 + plans/audit-2026-09-23/issues/A07.md | 87 ++ plans/audit-2026-09-23/issues/T02.md | 214 ++++ plans/audit-2026-09-23/issues/U08.md | 6 + plans/audit-2026-09-23/state.json | 39 +- 25 files changed, 1762 insertions(+), 15 deletions(-) create mode 100644 plans/audit-2026-09-23/a07-final-validation.json create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-analyze.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-coverage.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-final-focused.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-full.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-generate.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-generated-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-local-only-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-manifest-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-negative.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-python-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-review-transcript.md create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-ui-capture.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/a07-ui-overflow-before.log create mode 100644 plans/audit-2026-09-23/artifacts/a07/preparation-error-en.png create mode 100644 plans/audit-2026-09-23/artifacts/a07/preparation-error-ko.png create mode 100644 plans/audit-2026-09-23/artifacts/a07/preparation-partial-en.png create mode 100644 plans/audit-2026-09-23/artifacts/a07/preparation-partial-ko.png create mode 100644 plans/audit-2026-09-23/artifacts/a07/source-freeze.json create mode 100644 plans/audit-2026-09-23/issues/T02.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 83e79030..e651e645 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -105,3 +105,11 @@ D03최종main통합074d5fd6 원격Flutter882/analyze/88.50%,AndroidAPK/manifest - A07 root 리뷰 추가: 같은 run의 upcoming stream refresh가 새 ScheduleState.started를 만들 때 pending 배너 상태를 잃지 않아야 한다. 에이전트가 회귀 확인 중. - `/root/grill_t01` 신규 전담은 고정 e6c1438e 코드로 T01의 실제 문답을 시작했다. docs/T01만 소유하며 Flutter 실행 금지. T04는 문서 완료/게시, T03/T04와 CONTEXT 보완은 다음 docs 커밋 대상. - 다음 구현 C01→A08→A09→D01→D02→D04→D05→A10→U02→C08→U01→U03→T03→T04→T01→T02… 우선순위/의존성 계획 유지. 전부 완료되지 않았으며 active goal 계속. + +## 2026-09-24 A07 완료·C01 착수 최신 갱신 + +A07 source d73c7863a01343004ccadcd72064b563c464dd0d: 동결29파일과commitblob root재해시일치, 실제DB수정전3결함재현, focused56/full944,coverage88.67%(13289/14987),analyzer/generated/local-only/Python11/manifest통과. KOEN200%합성receipt캡처4장 및 실제스크롤/skip/다음단계 접근검증. DB버튼통합은실제Drift+latch로별도증명. source기기/CI아직별도. a07-final-validation.json 및artifacts/a07보존; #599본문실제추가리뷰/철회+결과exactreadback. + +T01#615,T02#616 게시하여30/64(34생성전). T02 P03의 schedule-start OS예약과A06 activeperiodic 전달범위를명확히분리하는후속문구검토중. C01#600 /root/implement_c01이workflow/typed결과/UI분리및previewclaim실제Drift회귀구현중이며Flutter독점. C01claim이후OS취소대기중일반writer변경은최종DBtransaction revision검사로overwrite방지하되기수행OS부작용을receipt에보존한다. 완전한writer fencing/staging/runtime전이는A09통합미완료로명시한다. /tmp/c01-review-transcript.md에실제문답기록. + +PR본문갱신은자동승인검토의거절로사용자승인대기중이며우회하지않는다. 로컬준비본문 /tmp/ontime-audit-pr.md. 해당한정승인대기와무관한승인된구현/상세이슈작업계속. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index cb02d4c2..8da04cec 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -28,7 +28,7 @@ | A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 요청별 후속 재조정/교체 직렬화 구현, 최종 원격795 tests/analyze·87.66%·APK 통과, 기기 대기 | | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 독립 journal·복구/host kill11, main통합 원격882 tests/88.50%·APK 통과,기기 대기 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | d06474d4 실제 타이머 전환 구현,47경계 및 원격929전체·88.79%·APK 통과,기기 대기 | -| A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | 실제3문답·추가 정정 포함, DB 원자적 시작·run 시각·실패 재시도 기준 확정 | +| A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | d73c7863 원자적 시작·partial retry 구현,944 tests/88.67%·KOEN200% 검증,CI·기기 대기 | | C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 실제3문답·staging/gate 보충 포함, 구체 workflow·부분 결과·통합 의존성 확정 | | A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 실제4문답·원자 저장/조회·충돌/멱등/소유권 기준 확정 | | A09 | [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | 실제4문답·staging/runtime 세대·복원 후 정리 기준 확정 | @@ -46,7 +46,9 @@ | T04 | [#614](https://github.com/DevKor-github/OnTime-front/issues/614) | 실제3문답·실제 진입점/독립 oracle·Auth 복구/삭제/세션 연결30개 검증 계약 | | T01 | [#615](https://github.com/DevKor-github/OnTime-front/issues/615) | 실제3문답·정상main/암호화저장소·OS수신/프로세스재실행·양플랫폼18개 E2E 행렬 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **29/64개**를 게시하고 원격 본문 동일성을 확인했으며 **35개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07 실제 DB 시작 결함3개를 재현해 다음 구현 중이다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +| T02 | [#616](https://github.com/DevKor-github/OnTime-front/issues/616) | 실제3문답·native15/플랫폼물리20행렬·OS경계/기기격리·실행증거 계약 | + +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **30/64개**를 게시하고 원격 본문 동일성을 확인했으며 **34개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200%를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/a07-final-validation.json b/plans/audit-2026-09-23/a07-final-validation.json new file mode 100644 index 00000000..641e5c15 --- /dev/null +++ b/plans/audit-2026-09-23/a07-final-validation.json @@ -0,0 +1,119 @@ +{ + "scope": "A07 #599 local source implementation and validation; not merge, release or physical device proof", + "source_freeze": { + "baseline": "c0907712429f5aa7a2ffbaf50332e665409dff7a", + "files": { + "lib/data/data_sources/early_start_session_local_data_source.dart": "a82657e5337aab46d99868f031156c71d56d823482682a65f7360ad2b227dfae", + "lib/data/repositories/schedule_repository_impl.dart": "3071ca881e33da7fb85684ee0898fdd5e5066051cf37e8b08eedd0acc361df54", + "lib/domain/entities/schedule_start_rejected.dart": "87b12570f7e622fd4490d7b46b9e9d2d24d0ce6ed192864888280ce614b0ecbc", + "lib/domain/repositories/schedule_repository.dart": "144ca813485e542b7a16e94780bbe85593a67f6a87a43391e351b62c8c69d0a2", + "lib/domain/use-cases/finish_schedule_use_case.dart": "2a4897f2cc28abaa3c8f50c252d8c8d9525a0c35a7e2bb55a8c36ef31089b7ac", + "lib/domain/use-cases/schedule_preparation_session_use_case.dart": "66426b3eff70d8d9dc78912dcaf782d60fa3e5e8c66a90a95a509d9f7f23c54d", + "lib/l10n/app_en.arb": "acb4d5cf83dc5132b40857e503677398ccca4d849a75454771b54995a5636c4a", + "lib/l10n/app_ko.arb": "25acbfed2188139d78f7b5f3fa14bdecddce66f805eaaaaa78e0f839550590f9", + "lib/l10n/app_localizations.dart": "8af7a8251e5931b5a6e2594c993471bd0f880d5d4d0e8d12a184b5e1c5717dd3", + "lib/l10n/app_localizations_en.dart": "9982c9681e6d4a3f6d5f0520fee91dbcb4109d9b4daf4d81c42b48cc9e8c704b", + "lib/l10n/app_localizations_ko.dart": "7c925f9487e86a3a64160ae34a1dfe744c19eba841ddba9a5a5040709d975d58", + "lib/presentation/alarm/components/alarm_screen_top_section.dart": "a4a3565323291193f8057cf417fd3916c71d46551b0f247211ddc7f745a061de", + "lib/presentation/alarm/components/preparation_step_tile.dart": "96a68057dc3b425d5be0f822ae0343113a06d51ac438181349dbc05da3305bcb", + "lib/presentation/alarm/screens/alarm_screen.dart": "c19dc1ca73f4377382fa4b236ab6a7c3958392b49a6e1f01079aa5ae3b2ebbf8", + "lib/presentation/alarm/screens/schedule_start_screen.dart": "903e90a4ca9e7cc541d4ed07309062f548ac44104bced8ccc95576dcb9a5b2ba", + "lib/presentation/app/bloc/schedule/schedule_bloc.dart": "a44e99a352f540d92d2d073f2ebe8529b589e5bf7e8c66b361c32f67493ad9e4", + "lib/presentation/app/bloc/schedule/schedule_event.dart": "3429adf54c131ab193e1a81da556c34cf38fd302c9b4117554b0b3f6460dc07e", + "lib/presentation/app/bloc/schedule/schedule_state.dart": "ac4926f5ddb2813638115c8a1aa6bf41f2b10c259b07c8f40bf6379596847a7c", + "test/data/repositories/schedule_start_transaction_test.dart": "4b42aebf7f5e7600c8eff235a31f945eedeb98ba6a3c5bc87c27f0e7fbbb8171", + "test/domain/use-cases/schedule_mutation_use_cases_test.dart": "e5ccf01f01226850377e864e5d9012c8be0983772873d23688ad54ee6ca57213", + "test/domain/use-cases/schedule_preparation_session_use_case_test.dart": "f434dabb6a6c6f5154d85d026d0dd81281dbdc3b9a70df4cd772a03a43e85559", + "test/domain/use-cases/timed_preparation_use_cases_test.dart": "08fc5ce01227d3e1a79ea17220aa08cecad9521c109c81c5b2a4a2896e4ec5d2", + "test/presentation/alarm/screens/preparation_flow_widget_test.dart": "889b8c1b42a5932c6ddd46b75526823701990f55e41930ed586c2b017fdc6e80", + "test/presentation/alarm/screens/schedule_start_commit_widget_test.dart": "f8e872f748bb4eb14a96a545240fdc072ecbe9c9fc3b83b8a8c63dbf524bbdc9", + "test/presentation/app/bloc/schedule/preparation_start_recovery_test.dart": "1af89ef608c92f1015996fee68e9e7e82b7615fc75d3fd70dee9ed52c307416d", + "test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart": "a8ff3397cadf360dca302f02b627b339fe8e7ffc7878e5ab7730ce8c03955e43", + "test/presentation/app/bloc/schedule/schedule_bloc_test.dart": "0c0ec915425269a620cfe50d3baecefb6fa17e361042ae0a4a29951a973d614b", + "test/presentation/shared/router/notification_launch_widget_test.dart": "08de5fe0ae1d328d22529228fe245a15024cd18311c4942e8bce57c241691c46", + "tool/qa/capture_preparation_start.dart": "170ea3f55f54030b2f7b6159c053704f15fd2d6a3c74d6c85518b337114fed45" + }, + "source_manifest_sha256": "dfedcb18495e5fc0d89bb659d24832ce70fb32a577b80f1a30df6c92ff9111de" + }, + "full_suite": { + "passed": 944, + "failed": 0, + "log": "/tmp/a07-full.log" + }, + "coverage": { + "percentage": 88.67, + "covered": 13289, + "lines": 14987, + "minimum": 80 + }, + "focused": { + "passed": 56, + "log": "/tmp/a07-final-focused.log" + }, + "analyzer": "No issues found", + "ui": { + "viewport": [ + 430, + 932 + ], + "text_scale": 2, + "raster_scale": 2, + "locales": [ + "ko", + "en" + ], + "scenarios": [ + "durable start failure", + "committed start partial recovery" + ], + "scroll": "actual list scroll, skip tap and next step hit-test passed in both locales", + "source": "synthetic receipt widget rendering; distinct from actual SQLite button tests" + }, + "limitations": [ + "No physical Android/iOS lifecycle or native delivery proof in A07", + "Existing English AlarmScreen Korean time and skip labels remain under U05", + "Minimum content heights are a scoped U08 follow-up, not completion of all accessibility work", + "No merge/deployment/store claims; remote CI awaits root push" + ], + "raw_artifacts": { + "/tmp/a07-negative.log": "f47b119bb4803be8e2253bc9335bbfed31d7eb1c8bef3f90aba4d50cc0b9c389", + "/tmp/a07-final-focused.log": "fbbb337de723e92f717d417d695f2f5584d8a84f622f03cc7eff1021585e69c4", + "/tmp/a07-analyze.log": "858887ef606eccd00cce0c9798f8aad50c74b2d0f6c49d0cfd4c2c7eb60abf61", + "/tmp/a07-full.log": "c231bc5ff52127c70b02834447394159a37607c509234c7fcb34228f3b502c34", + "/tmp/a07-coverage.log": "66cd2f9e8016107f594e03bad61227b49514a28bfa26d6f2d024c3e4eebfe77f", + "/tmp/a07-generate.log": "46bb94575fde53480b43e7f1cf813d0d09699a364ccd68f1e825762d5748dec0", + "/tmp/a07-python-policy.log": "4fdc1df0b7744216895078fbfd0411918951ced6f104fd7c2d803096adf589a8", + "/tmp/a07-manifest-policy.log": "60ac45f9be8ec739ed709fa43c33c8f51a94665b6bdea633dd65e27529d13c01", + "/tmp/a07-generated-policy.log": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "/tmp/a07-local-only-policy.log": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "/tmp/a07-ui-capture.log": "0328208f9d867befcdf536ac4611406cd8e5f2be57b5f079f086605e1b70a7f9", + "/tmp/a07-ui-overflow-before.log": "bbe91889f4bf3370fa98fb6225302bdd2d13caddd7d7d0509c52eb845db3f1e6", + "/tmp/a07-review-transcript.md": "99e698672aec56a2dbf2e6808bb4d52664e3a30b12585e8ef6dc8b9311fdfe5d", + "/tmp/a07-ui/preparation-partial-en.png": "2418716a1e763c82fc55b19feedee9082d5c8ef06d8e5b6161cd3b699c68979a", + "/tmp/a07-ui/preparation-error-ko.png": "eab4d45aa4048033f3a2f40d462f81920a275034cf286e1db70d3459a38ffc89", + "/tmp/a07-ui/preparation-partial-ko.png": "bd319f9ce92300998d116b60e8f1a10b3e1b786d38f7cbf864474287b0670068", + "/tmp/a07-ui/preparation-error-en.png": "bbe86dc327626ea5838c9489015ee5039f605533c5dc9624cdc2d1204a655897" + }, + "artifacts": { + "plans/audit-2026-09-23/artifacts/a07/a07-negative.log": "a70fa3f470666108d5b09f1812b206dca70a09c048aa9ec8af469d7ce269a754", + "plans/audit-2026-09-23/artifacts/a07/a07-final-focused.log": "fbbb337de723e92f717d417d695f2f5584d8a84f622f03cc7eff1021585e69c4", + "plans/audit-2026-09-23/artifacts/a07/a07-analyze.log": "e14cd93bf6c2391ea351f1e339dfb189231948b63a2fb0d98d6808596574e80f", + "plans/audit-2026-09-23/artifacts/a07/a07-full.log": "c231bc5ff52127c70b02834447394159a37607c509234c7fcb34228f3b502c34", + "plans/audit-2026-09-23/artifacts/a07/a07-coverage.log": "66cd2f9e8016107f594e03bad61227b49514a28bfa26d6f2d024c3e4eebfe77f", + "plans/audit-2026-09-23/artifacts/a07/a07-generate.log": "eb2f5668150f568ebaf5013c746dee250b02be59b29decad99efcecaead22f08", + "plans/audit-2026-09-23/artifacts/a07/a07-python-policy.log": "4fdc1df0b7744216895078fbfd0411918951ced6f104fd7c2d803096adf589a8", + "plans/audit-2026-09-23/artifacts/a07/a07-manifest-policy.log": "60ac45f9be8ec739ed709fa43c33c8f51a94665b6bdea633dd65e27529d13c01", + "plans/audit-2026-09-23/artifacts/a07/a07-generated-policy.log": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "plans/audit-2026-09-23/artifacts/a07/a07-local-only-policy.log": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "plans/audit-2026-09-23/artifacts/a07/a07-ui-capture.log": "0328208f9d867befcdf536ac4611406cd8e5f2be57b5f079f086605e1b70a7f9", + "plans/audit-2026-09-23/artifacts/a07/a07-ui-overflow-before.log": "302c9305186a6f871bbcd92791bfcac82e6ebaf171bb881a103e9544e99d0a7c", + "plans/audit-2026-09-23/artifacts/a07/a07-review-transcript.md": "99e698672aec56a2dbf2e6808bb4d52664e3a30b12585e8ef6dc8b9311fdfe5d", + "plans/audit-2026-09-23/artifacts/a07/preparation-partial-en.png": "2418716a1e763c82fc55b19feedee9082d5c8ef06d8e5b6161cd3b699c68979a", + "plans/audit-2026-09-23/artifacts/a07/preparation-error-ko.png": "eab4d45aa4048033f3a2f40d462f81920a275034cf286e1db70d3459a38ffc89", + "plans/audit-2026-09-23/artifacts/a07/preparation-partial-ko.png": "bd319f9ce92300998d116b60e8f1a10b3e1b786d38f7cbf864474287b0670068", + "plans/audit-2026-09-23/artifacts/a07/preparation-error-en.png": "bbe86dc327626ea5838c9489015ee5039f605533c5dc9624cdc2d1204a655897" + }, + "artifact_note": "Original hashes retained in raw_artifacts; stored log copies normalize trailing whitespace only.", + "implementation_commit": "d73c7863a01343004ccadcd72064b563c464dd0d", + "root_committed_blob_verification": "All 29 frozen source/test/QA files match the committed blobs" +} diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-analyze.log b/plans/audit-2026-09-23/artifacts/a07/a07-analyze.log new file mode 100644 index 00000000..e0489911 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-analyze.log @@ -0,0 +1,2 @@ +Analyzing OnTime-front... +No issues found! (ran in 1.9s) diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-coverage.log b/plans/audit-2026-09-23/artifacts/a07/a07-coverage.log new file mode 100644 index 00000000..ffdad6b4 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-coverage.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Coverage: 88.67% (13289/14987 lines) diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-final-focused.log b/plans/audit-2026-09-23/artifacts/a07/a07-final-focused.log new file mode 100644 index 00000000..195e23f2 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-final-focused.log @@ -0,0 +1,58 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: concurrent and repeated starts preserve first timestamp and one revision +00:00 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=false missing=false without overwriting durable first start +00:00 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=true missing=false without overwriting durable first start +00:00 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=false missing=true without overwriting durable first start +00:00 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: completed and deleted occurrences cannot restart +00:00 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_start_recovery_test.dart: partial start survives same-run emissions; timer and skip retain memory until one explicit retry +00:00 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:01 +56: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-full.log b/plans/audit-2026-09-23/artifacts/a07/a07-full.log new file mode 100644 index 00000000..30ed916e --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-full.log @@ -0,0 +1,950 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:02 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:03 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:03 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:03 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:03 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:04 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: only complete iOS OS observations resolve reconstructed uncertainty +00:04 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeUnmapped remains partial without guessing ownership +00:04 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeError remains partial without guessing ownership +00:04 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS fallbackError remains partial without guessing ownership +00:04 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS unownedNotification remains partial without guessing ownership +00:04 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: secure reset marker resumes deletion but Android uncertainty remains +00:04 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: explicit reset after damage cleans quarantine only after complete receipt +00:04 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":2} +00:04 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"complete","completed":[],"ownership":[]} +00:04 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"none","completed":[],"ownership":[{"provider":"localNotification","scheduleId":"a","id":1,"pending":true},{"provider":"localNotification","scheduleId":"b","id":1,"pending":true}]} +00:04 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: read I/O failure does not quarantine or authorize deletion +00:04 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryMarked recovers through a new owner +00:04 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:04 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: pending reset preserves its ownership and bypasses unrelated privacy cleanup +00:04 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:04 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:04 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:04 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:04 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at verified recovers through a new owner +00:04 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at renamed recovers through a new owner +00:04 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at readBack recovers through a new owner +00:04 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryVerified recovers through a new owner +00:04 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryPublished recovers through a new owner +00:04 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: first pending quarantine crash resumes without deleting user data +00:04 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: unverified reconstructed reset journal cannot start deletion or OS cancellation +00:04 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: true +00:04 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: false +00:04 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: normal civil time resolves to one absolute occurrence +00:04 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: spring-forward gap has no valid occurrence +00:04 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: fall-back overlap exposes first and second occurrence +00:05 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:06 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:06 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:06 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:06 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:06 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:06 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:07 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:07 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:07 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:07 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:07 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:07 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:07 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:07 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:07 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:07 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:07 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:07 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:07 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:07 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:08 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:09 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: initializeLaunchHandling dispatches cold-start alarm payload +00:09 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload dispatches resumed alarm payload +00:09 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities maps native alarm provider capabilities +00:09 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats missing platform implementation as unsupported +00:09 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats native platform errors as unsupported +00:09 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats null native response as unsupported +00:09 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: checkPermission and requestPermission map native wire values +00:09 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat missing platform implementation as unsupported +00:09 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat native platform errors as unsupported +00:09 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm sends the native alarm contract payload +00:09 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm exposes permission failures as scheduling exception +00:09 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm maps unsupported and generic native errors +00:09 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm sends cancellation payload for scheduled records +00:09 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm reports missing native platform as unconfirmed +00:09 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm maps native platform failures to scheduling exception +00:09 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelAllNativeAlarms cancels every provided record in order +00:09 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload ignores invalid and failed payload reads +00:09 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=false snapshot after persistence +00:09 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=true snapshot after persistence +00:09 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=false snapshot after persistence +00:09 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=true snapshot after persistence +00:09 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: legacy and corrupted content cannot bypass reconciliation at either provider +00:09 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancel uses the plugin API for pending and presented IDs, never cancelAll +00:09 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancellation errors are visible to reconciliation +00:09 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/app_metadata_service_test.dart: package info metadata provider returns runtime app metadata +00:10 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: owned legacy pending is cancelled without clearing step notifications +00:10 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: cancel failure preserves only ownership tombstone and retry converges +00:10 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:10 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: successful cancel call without readback removal is not success +00:10 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: cold launch collects once and concurrent initialization is single flight +00:10 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: null +00:10 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:10 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:10 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: getter failure releases single flight and callback still works before retry +00:10 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback B arriving while cold A getter waits wins, raw legacy fields removed +00:10 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback before delegate installation preserves only safe latest payload +00:10 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: Noop to installed delegate cannot let slow cold A overwrite native B +00:10 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: data replacement invalidates delayed initial launch +00:10 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:10 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android read failure is unknown, not absent +00:10 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cancel needs public success and post read-back +00:10 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache absence does not hide cancel failure +00:10 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android remaining ID and failed post query are unconfirmed cancellations +00:10 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS OS observation preserves source and minimal ownership +00:10 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS read failure is unknown, not absent +00:10 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cancel needs public success and post read-back +00:10 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cache absence does not hide cancel failure +00:10 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS remaining ID and failed post query are unconfirmed cancellations +00:10 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit observation rejects unavailable, malformed and unrelated IDs +00:10 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit already absent is idempotent; present cancel requires fresh absence +00:10 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:10 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: platform cleanup failure is visible and retryable, without raw content +00:10 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: DB-unavailable cleanup removes legacy raw and does not touch unrelated data +00:11 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: localizedNotificationText selects Korean text only for Korean locale +00:11 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: isScheduleAlarmPayload detects native alarm payloads without suppressing schedule notifications +00:11 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule notification payload to the schedule start screen +00:11 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes five-minute prompts as early-start schedule starts +00:11 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule and preparation updates to the alarm screen +00:11 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString ignores null, invalid, and unrelated payloads +00:11 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: invalid schedule hints never route to nearest preparation +00:11 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForData routes decoded local payload data with the same rules +00:11 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:11 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: explicit exact denial retries once and returns actual approximate receipt +00:11 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unrelated provider error is never converted to approximate success +00:11 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: failed approximate retry stays failed and is not retried again +00:11 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unknown timing permission uses approximate rather than claiming exact +00:11 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: display denial blocks scheduling even with exact access +00:11 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: settings response is not proof of a grant: permission is reread +00:11 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: iOS timing access is unsupported without calling Android plugin +00:11 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:11 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:11 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:11 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: concurrent and repeated starts preserve first timestamp and one revision +00:11 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=false missing=false without overwriting durable first start +00:11 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=true missing=false without overwriting durable first start +00:12 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=false missing=true without overwriting durable first start +00:12 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: completed and deleted occurrences cannot restart +00:12 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real storage retains every failed platform ownership for the same Schedule +00:12 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:12 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=true +00:12 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: loadAll returns the current local registry +00:12 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: upsert replaces an existing schedule record and keeps others +00:12 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: deleteByScheduleId removes only the requested schedule +00:12 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: replaceAll deduplicates actual platform identity and deleteAll clears storage +00:12 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:12 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:12 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:12 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:12 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:12 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:12 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:12 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk changes preserve only overridden fields and preparation +00:12 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: ending following leaves active and historical rows and their preparations intact +00:12 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: finite conflicts require explicit exclusion and excluded slots consume count +00:12 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: place edits never rename another occurrence or historical place +00:12 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk edit preserves active preparation and consumes its original count +00:12 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: stale first occurrence approval asks for review without changing durable data +00:12 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:13 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:13 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:13 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:13 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:13 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:13 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:13 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:13 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: multiple preparation steps are a no-op even when caller omits links +00:13 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedule date ranges as start-inclusive and end-exclusive +00:13 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: missing profile rejects updates and never reports a successful mutation +00:13 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:13 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: empty profile starts without a durable revision and creation is race safe +00:13 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: raw revision update notifies DB watchers and failed profile write publishes no success +00:13 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_data_source_contract_test.dart: schedule local data source contract does not expose domain entities +00:14 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:14 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:14 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:14 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:14 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:14 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:14 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:14 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:14 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:14 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:14 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:14 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:14 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:14 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps users to and from database rows preserving profile values +00:14 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:14 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:14 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps places and preparation steps to database rows +00:14 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:15 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getUserById returns null for an unknown user +00:15 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getAllUsers returns all persisted users as domain entities +00:15 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return an empty preparation list when user has no steps +00:15 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:15 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps when rows are out of order +00:15 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should keep remaining preparation steps when a link is broken +00:15 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return each preparation step once when links form a cycle +00:15 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should return one preparation step by id +00:15 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:15 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:15 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: updatePreparationUser should update a preparation step +00:15 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:16 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:16 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:16 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:16 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:16 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:16 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:16 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:16 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:16 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:16 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:16 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: updatePreparationUser should update a preparation step +00:16 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations createPreparationSchedule stores steps as an ordered linked list +00:16 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns empty for no steps +00:16 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId orders three linked steps when rows are out of order +00:16 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId keeps remaining steps when a link is broken +00:16 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns each step once when links form a cycle +00:16 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById returns the stored step contract +00:16 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:16 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:16 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:16 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:16 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: deleting a counted slot never extends or fills the series +00:16 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: last day is distinct from 31st, including leap February +00:16 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fifth weekday skips missing months while last weekday does not +00:16 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: weekly matching starts inclusively without adding the anchor day +00:16 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: daily intervals keep civil time and include the end date +00:16 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: spring gap is skipped and does not consume the count +00:16 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fall overlap requires an explicit first or second choice +00:16 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: new series starts at a future preparation and counts from there +00:16 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: range queries preserve original count and stable slot keys +00:16 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: unknown zone and exhausted search never report success +00:16 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: invalid and ambiguous rule definitions are rejected +00:16 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:16 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:16 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:16 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation +00:16 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:16 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:16 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: skips a just-missed alarm instead of scheduling catch-up +00:16 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:16 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:16 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:16 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:16 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:16 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:16 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:16 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:16 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:16 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:16 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:16 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:16 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:16 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:16 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:16 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:16 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:16 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:16 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:16 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:16 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:16 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:16 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:16 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:16 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:16 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:16 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:16 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:16 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:16 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:16 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:16 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:16 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:16 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:16 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:16 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:17 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:17 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: deleted and finished schedules accept reconciliation before awaiting cancellation +00:17 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: reconciliation failures remain best-effort +00:17 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:17 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:17 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:17 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:17 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:17 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:17 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:17 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:17 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:17 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:17 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:18 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: pending callers share failure, then retry the same instance and timestamp +00:18 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: snapshot failure is partial and retry preserves a newer skip projection +00:18 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: replacement generation does not reuse an old flight or write old runtime +00:18 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: marker failure keeps durable run and a bounded retry preserves its clock +00:18 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish while start cleanup is pending cannot resurrect the run +00:18 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: dispose detaches the shared listener without releasing in-flight ownership +00:18 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:18 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure does not undo committed start or skip its snapshot +00:18 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure after finish still clears completed runtime +00:18 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:18 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:18 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:18 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:18 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:18 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:18 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:18 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:18 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:18 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:19 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:19 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:19 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:19 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:19 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:19 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:20 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: a previous civil date in a negative offset still supplies a future alarm +00:20 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: each cutoff finishes without waiting for later edits to become quiet +00:20 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failed snapshot completes its cutoff and still serves a newer request +00:20 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failure alone stops; a request from completion starts another pass +00:20 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: replacement waits for the actual late native Future before same-ID reuse +00:20 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: caller timeout cannot release ownership of an unreturned native Future +00:20 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:20 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:20 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:20 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:20 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:20 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:20 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: request after schedule snapshot receives its own latest pass +00:20 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=false +00:20 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:20 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:20 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: a registry-owned ID with a conflicting route is cancelled before repair +00:20 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=true +00:20 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=true +00:20 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=false +00:20 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=false +00:20 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native revoke and grant switch providers after cancelling the old registration +00:20 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:20 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:20 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:20 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:20 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:20 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:20 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:20 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: confirmed fallback orphans are targeted; unrelated and unknown ownership survive +00:20 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:20 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native failure uses only the actual fallback receipt after native cleanup +00:20 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed native write cleanup forbids fallback and keeps cancellation identity +00:20 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:20 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: off retains a cleanup warning and denial never becomes precise +00:20 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: no future registrations never becomes precise from capability +00:20 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: iOS alarm and notification language remains capability appropriate +00:20 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:20 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: empty user exposes null convenience values +00:21 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:21 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:21 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:21 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:21 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:21 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:21 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities calculates left time before leave with explicit now +00:21 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports late status after leave time has passed +00:21 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities fast-forwards preparation steps for late entry +00:21 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities single preparation step tracks elapsed time and diagnostics +00:21 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities marks all steps done for very late entry inside schedule window +00:21 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports display helpers for current and completed steps +00:21 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:21 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:21 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:21 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:21 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:21 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:21 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:21 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:21 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:21 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:21 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:21 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: minimal progress hydrates names/durations from current source only +00:21 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy exact fingerprint additionally requires all step fields +00:21 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy launch becomes a minimal versioned route, never a start command +00:22 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:22 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:22 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:22 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:22 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:22 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (setUpAll) +00:22 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:22 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:23 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:23 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:23 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: compact height clamps calendar rows without overflow +00:23 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:24 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:24 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:24 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits without scroll at 1.3 text scale +00:24 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: HomeScreenTmp subscribes monthly bloc for today +00:24 +549: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits when today schedule exists +00:24 +550: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:24 +551: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:24 +552: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:24 +553: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:24 +554: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:24 +555: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +556: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +557: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +558: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +559: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +560: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +561: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +562: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +563: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +564: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +565: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +566: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +568: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +569: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +570: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +571: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +572: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +573: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +574: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +576: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:25 +577: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +578: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +579: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +580: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +581: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +582: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +583: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +584: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +585: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +586: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +588: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +589: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +590: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +591: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +592: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +593: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +594: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:25 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden after preparation start time +00:25 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:25 +597: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:25 +598: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: delete action stays available for started unfinished schedule +00:25 +599: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:25 +600: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:26 +601: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:26 +602: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:26 +603: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:26 +604: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:26 +605: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:26 +606: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:26 +607: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:26 +608: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:26 +609: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:26 +610: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:26 +611: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +612: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +613: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +614: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +615: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +616: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +617: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +618: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +619: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +620: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:26 +621: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:27 +622: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:27 +623: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: clock jump 0:00:03.000000 does not submit stale step +00:27 +624: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:27 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:27 +626: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:27 +627: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:27 +628: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:27 +629: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:27 +630: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow emits notExists when there is no upcoming schedule +00:27 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow emits notExists when there is no upcoming schedule +00:27 +632: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/notification_prompt_race_test.dart: late null-nearest handler cannot overwrite a newer notification owner +00:27 +633: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/notification_prompt_race_test.dart: late null-nearest handler cannot overwrite a newer notification owner +00:27 +634: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +635: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +636: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +637: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +638: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +639: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +640: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +641: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +642: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +643: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +644: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +645: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +646: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +647: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +648: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +649: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +650: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +651: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +652: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +653: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +654: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +655: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +656: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +657: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +658: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +659: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +660: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +661: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +662: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +663: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +664: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +665: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +666: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:27 +667: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:27 +668: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:27 +669: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:27 +670: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:27 +671: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow early start from upcoming emits started and marks early session +00:27 +672: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm launch start action keeps cached schedule on start prompt +00:27 +673: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm launch start action keeps stale-fingerprint cached schedule on start prompt +00:27 +674: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +675: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +676: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +677: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +678: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +679: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +680: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +681: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +682: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +683: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:28 +684: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:29 +685: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow same schedule re-emission preserves started state when early-started +00:29 +686: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish clears timed preparation and early session state +00:29 +687: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow confirmation-required run stays stopped after prompt until explicit start +00:29 +688: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow mismatched fingerprint invalidates cached progress +00:29 +689: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start when early session and snapshot exist +00:29 +690: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start with only early session derives elapsed from session start +00:29 +691: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt loads schedule details and arms the prompt +00:29 +692: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects ended schedules and clears their alarm +00:29 +693: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects stale fingerprint unless it starts prep +00:29 +694: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm prompt validation failure clears only non-start prompts +00:29 +695: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish failure keeps the active schedule visible +00:29 +696: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: schedule events expose equality props for bloc deduping +00:29 +697: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatTime describes elapsed seconds in Korean units +00:29 +698: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatTimeTimer uses timer display with hours when needed +00:29 +699: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatEalyLateTime reports absolute early or late minutes +00:29 +700: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatElapsedTime keeps seconds two-digit padded +00:29 +701: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:29 +702: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default marker when a scheduled day is not selected +00:29 +703: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default empty-day behavior when there are no events +00:29 +704: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:30 +705: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:30 +706: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:30 +707: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:30 +708: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:30 +709: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:30 +710: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:30 +711: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:30 +712: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: showTwoActionDialog returns primary result +00:30 +713: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:30 +714: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:30 +715: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:30 +716: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:31 +717: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:31 +718: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (setUpAll) +00:31 +718: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:31 +719: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:31 +720: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:31 +721: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:31 +722: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:31 +723: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:31 +724: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:31 +725: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (tearDownAll) +00:31 +725: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:32 +726: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route exits back toward its entry edge +00:32 +727: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: bottom navigation routes exit back toward their entry edge +00:32 +728: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: (setUpAll) +00:32 +728: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:32 +729: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:32 +730: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:32 +731: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:32 +732: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +733: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +734: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +735: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +736: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +737: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +738: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +739: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +740: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +741: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +742: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +743: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +744: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +745: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:33 +746: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:34 +747: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:34 +748: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:34 +749: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:34 +750: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:34 +751: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:34 +752: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:34 +753: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:34 +754: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:34 +755: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:34 +756: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:34 +757: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:34 +758: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:35 +759: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:35 +760: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:35 +761: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:35 +762: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:35 +763: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:35 +764: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:35 +765: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:35 +766: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:35 +767: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:35 +768: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:35 +769: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +770: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +771: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +772: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +773: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +774: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +775: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +776: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +777: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +778: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +779: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +780: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +781: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +782: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +783: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +784: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +785: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +786: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:36 +787: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:37 +788: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:37 +789: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: restricted notification state can open system settings +00:37 +790: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:37 +791: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:37 +792: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:37 +793: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:37 +794: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:37 +795: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:37 +796: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:37 +797: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:38 +798: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:38 +799: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:38 +800: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:38 +801: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:38 +802: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:38 +803: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:38 +804: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:38 +805: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:38 +806: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:39 +807: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:39 +808: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:39 +809: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:39 +810: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/components/preparation_step_list_widget_test.dart: renders ordered preparation steps and wires skip action +00:39 +811: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:39 +812: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:39 +813: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:39 +814: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:39 +815: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:39 +816: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +817: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +818: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +819: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +820: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +821: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +822: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +823: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +824: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +825: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:39 +826: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:40 +827: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog finish triggers finish flow +00:40 +828: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog finish triggers finish flow +00:40 +829: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog finish triggers finish flow +00:40 +830: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog finish triggers finish flow +00:40 +831: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog finish triggers finish flow +00:40 +832: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:40 +833: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:40 +834: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:40 +835: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets stale notification after schedule end should redirect safely (spec-first) +00:40 +836: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: uses alarm language for iOS AlarmKit capability +00:40 +837: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: dismiss keeps notification delivery when fallback is available +00:40 +838: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:41 +839: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:41 +840: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:42 +841: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:42 +842: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:42 +843: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:42 +844: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:42 +845: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:42 +846: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:42 +847: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:42 +848: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:42 +849: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:43 +850: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:43 +851: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:43 +852: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:43 +853: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:43 +854: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:43 +855: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:43 +856: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:43 +857: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:43 +858: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: scope defaults to only this occurrence and supports following scope +00:43 +859: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:43 +860: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: failed reset remains recoverable and never reports success +00:43 +861: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: partial reset opens recovery instead of reporting completion +00:43 +862: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:44 +863: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:44 +864: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:44 +865: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:44 +866: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:44 +867: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +868: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +869: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +870: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +871: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +872: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +873: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +874: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +875: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +876: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +877: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +878: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +879: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +880: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +881: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +882: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +883: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +884: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +885: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +886: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +887: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:44 +888: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:45 +889: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:45 +890: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:45 +891: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:45 +892: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: fills the bottom safe area while keyboard is hidden +00:45 +893: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: paints a white backplate behind the iOS keyboard inset +00:45 +894: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: does not add keyboard backplate while keyboard is hidden +00:46 +895: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +896: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +897: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +898: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +899: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +900: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +901: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +902: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +903: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +904: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +905: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +906: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:46 +907: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:47 +908: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:47 +909: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:47 +910: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:47 +911: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: shows explicit choices for a repeated DST time +00:47 +912: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:47 +913: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:47 +914: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:47 +915: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:47 +916: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:48 +917: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:48 +918: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:49 +919: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:49 +920: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:49 +921: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:49 +922: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:49 +923: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:49 +924: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:49 +925: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:49 +926: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_create_list_test.dart: create list forwards edits, reorder changes, and create request +00:49 +927: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:50 +928: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +929: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +930: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +931: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +932: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +933: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +934: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +935: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +936: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +937: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:50 +938: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:51 +939: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:51 +940: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation errors explain invalid name and preparation time +00:51 +941: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +00:51 +942: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +00:51 +943: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +00:52 +944: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-generate.log b/plans/audit-2026-09-23/artifacts/a07/a07-generate.log new file mode 100644 index 00000000..73de6178 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-generate.log @@ -0,0 +1,35 @@ +W These options have been removed and were ignored: --delete-conflicting-outputs + 0s freezed on 488 inputs; lib/core/backup/backup_crypto.dart +W SDK language version 3.12.0 is newer than `analyzer` language version 3.10.0. Run `flutter packages upgrade`. + 5s freezed on 488 inputs: 3 skipped, 1 no-op; spent 5s analyzing; lib/core/backup/recurring_backup_data.dart + 6s freezed on 488 inputs: 296 skipped, 27 no-op; spent 6s analyzing; lib/presentation/shared/router/route_arguments.dart + 6s freezed on 488 inputs: 457 skipped, 31 no-op; spent 6s analyzing + 0s json_serializable on 976 inputs; lib/core/backup/backup_crypto.dart + 1s json_serializable on 976 inputs: 837 skipped, 139 no-op + 0s drift_dev on 3904 inputs; lib/core/backup/backup_crypto.dart + 0s drift_dev on 3904 inputs: 1312 skipped, 187 same; lib/presentation/recurring/recurring_management_screen.freezed.dart + 0s drift_dev on 3904 inputs: 3487 skipped, 278 same, 139 no-op + 0s source_gen:combining_builder on 1952 inputs; lib/core/backup/backup_crypto.dart + 0s source_gen:combining_builder on 1952 inputs: 1952 skipped + 0s mockito:mockBuilder on 1144 inputs; test/core/backup/backup_crypto_test.dart + 0s mockito:mockBuilder on 1144 inputs: 1074 skipped, 70 no-op + 0s injectable_generator:injectable_builder on 5048 inputs; lib/core/backup/backup_crypto.dart + 0s injectable_generator:injectable_builder on 5048 inputs: 2444 skipped, 1 output, 25 same, 36 no-op; lib/presentation/shared/components/step_progress.dart.types.temp.g.dart + 0s injectable_generator:injectable_builder on 5048 inputs: 4909 skipped, 1 output, 25 same, 113 no-op + 0s injectable_generator:injectable_config_builder on 5048 inputs; lib/core/backup/backup_crypto.dart +W injectable_generator:injectable_config_builder on lib/core/di/di_setup.dart: + Missing dependencies in on_time_front/core/di/di_setup.dart + + [AlarmRepositoryImpl] depends on unregistered type [RecurringScheduleRepository] from package:on_time_front/domain/repositories/recurring_schedule_repository.dart + [ScheduleRepositoryImpl] depends on unregistered type [RecurringScheduleRepository] from package:on_time_front/domain/repositories/recurring_schedule_repository.dart + [CreateScheduleFormSubmissionUseCase] depends on unregistered type [RecurringSchedulesUseCase] from package:on_time_front/domain/use-cases/recurring_schedules_use_case.dart + [UpdateScheduleFormSubmissionUseCase] depends on unregistered type [RecurringSchedulesUseCase] from package:on_time_front/domain/use-cases/recurring_schedules_use_case.dart + [AuthBloc] depends on unregistered type [RuntimePrivacyMigration] from package:on_time_front/core/services/runtime_privacy_migration.dart + [ScheduleFormBloc] depends on unregistered type [RecurringSchedulesUseCase] from package:on_time_front/domain/use-cases/recurring_schedules_use_case.dart + + Did you forget to annotate the above class(s) or their implementation with @injectable? + or add the right environment keys? + ------------------------------------------------------------------------ + + 0s injectable_generator:injectable_config_builder on 5048 inputs: 5016 skipped, 1 output, 31 no-op + Built with build_runner/aot in 10s; wrote 305 outputs. diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-generated-policy.log b/plans/audit-2026-09-23/artifacts/a07/a07-generated-policy.log new file mode 100644 index 00000000..01eef09f --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-generated-policy.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Generated Dart policy is aligned: outputs are ignored and untracked. diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-local-only-policy.log b/plans/audit-2026-09-23/artifacts/a07/a07-local-only-policy.log new file mode 100644 index 00000000..71a9ce0a --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-local-only-policy.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Local-only product boundary verified. diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-manifest-policy.log b/plans/audit-2026-09-23/artifacts/a07/a07-manifest-policy.log new file mode 100644 index 00000000..29c18cf1 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-manifest-policy.log @@ -0,0 +1 @@ +{"manifest": "android/app/src/main/AndroidManifest.xml", "sha256": "6b16f0fbaf8febcc2f9647450e40ca498462e2142f279c90666f86f77f14f210", "failures": []} diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-negative.log b/plans/audit-2026-09-23/artifacts/a07/a07-negative.log new file mode 100644 index 00000000..71322ae4 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-negative.log @@ -0,0 +1,38 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart +00:00 +0: a failed revision write rolls back the schedule transition and retry succeeds +00:00 +0 -1: a failed revision write rolls back the schedule transition and retry succeeds [E] + Expected: false + Actual: + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/data/repositories/schedule_start_transaction_test.dart 27:5 main. + +00:00 +0 -1: concurrent and repeated starts preserve first timestamp and one revision +00:00 +0 -2: concurrent and repeated starts preserve first timestamp and one revision [E] + Expected: <2> + Actual: <4> + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/data/repositories/schedule_start_transaction_test.dart 43:5 main. + +00:00 +0 -2: completed and deleted occurrences cannot restart +00:00 +0 -3: completed and deleted occurrences cannot restart [E] + Expected: throws anything + Actual: '> + Which: emitted + + package:matcher expectLater + package:flutter_test/src/widget_tester.dart 507:8 expectLater + test/data/repositories/schedule_start_transaction_test.dart 50:11 main. + ===== asynchronous gap =========================== + dart:async _CustomZone.registerUnaryCallback + test/data/repositories/schedule_start_transaction_test.dart 48:5 main. + +00:00 +0 -3: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: completed and deleted occurrences cannot restart + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: concurrent and repeated starts preserve first timestamp and one revision diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-python-policy.log b/plans/audit-2026-09-23/artifacts/a07/a07-python-policy.log new file mode 100644 index 00000000..d2a81b93 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-python-policy.log @@ -0,0 +1,16 @@ +test_boot_permission_must_not_expire_on_newer_android (test_android_notification_manifest.NotificationManifestTest.test_boot_permission_must_not_expire_on_newer_android) ... ok +test_disabled_application_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_disabled_application_is_rejected) ... ok +test_duplicate_receiver_declaration_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_duplicate_receiver_declaration_is_rejected) ... ok +test_exported_disabled_or_renamed_components_are_rejected (test_android_notification_manifest.NotificationManifestTest.test_exported_disabled_or_renamed_components_are_rejected) ... ok +test_invalid_xml_is_reported_as_failure (test_android_notification_manifest.NotificationManifestTest.test_invalid_xml_is_reported_as_failure) ... ok +test_missing_boot_actions_are_individually_rejected (test_android_notification_manifest.NotificationManifestTest.test_missing_boot_actions_are_individually_rejected) ... ok +test_missing_delivery_component_is_rejected_even_if_named_in_comment (test_android_notification_manifest.NotificationManifestTest.test_missing_delivery_component_is_rejected_even_if_named_in_comment) ... ok +test_missing_exported_attribute_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_missing_exported_attribute_is_rejected) ... ok +test_receiver_must_be_direct_child_of_application (test_android_notification_manifest.NotificationManifestTest.test_receiver_must_be_direct_child_of_application) ... ok +test_source_manifest_can_deliver_and_restore_notifications (test_android_notification_manifest.NotificationManifestTest.test_source_manifest_can_deliver_and_restore_notifications) ... ok +test_valid_contract_without_explicit_enabled_is_accepted (test_android_notification_manifest.NotificationManifestTest.test_valid_contract_without_explicit_enabled_is_accepted) ... ok + +---------------------------------------------------------------------- +Ran 11 tests in 0.003s + +OK diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-review-transcript.md b/plans/audit-2026-09-23/artifacts/a07/a07-review-transcript.md new file mode 100644 index 00000000..da184a66 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-review-transcript.md @@ -0,0 +1,63 @@ +# A07 구현 중 실제 추가 리뷰 문답 + +## DB 최초 시각과 Preparation Run 시각 + +root: +> root독립리뷰에서실제결함발견: _startEarly가 _scheduleRepository.startSchedule(...startedAt:run.startedAt)의 DateTime 반환을버리고 marker/snapshot/receipt에run.startedAt를계속사용합니다. DB가이미시작됐거나새screen/runOwner/새instance재시도면repo는기존firstStartedAt를반환하므로DB/run/timer시각이달라집니다. authoritative committed timestamp를반환값에서받아후속모든단계·receipt·projection에사용하도록수정하고, 실제DB이미startedAt=T0인상태에서새usecase/owner가T1으로재시도해T0보존하는회귀추가해주세요. _runs owner교체중이전commit과새시도가경합하는case도같은불변식. 신규첫시도실패후동일intenttimestamp유지와모순없도록candidate vs committed시각구분. + +implement_a07: +> 이 지점은 A07 Q2 보충정정/Q3 최종 계약과 구별이 필요합니다. 계약: schedule.startedAt 최초 durable 사실은 보존하지만 A11 재확인 후 명시적 새 run은 독립 T1, DB T0만으로 active run 복원 금지입니다. 현재 runOwner 교체는 새 explicit intent로 취급합니다. 권고 수정: repository 반환 T0를 별도 durableStartedAt receipt로 유지; 유효한 동일 runtime/same run retry는 저장된 run.startedAt T0, 명시적 재확인/새 prompt의 새 run은 T1을 사용합니다. 새 usecase+DB T0만 있고 runtime없는 경우는 자동 T0 run 복원하지 않습니다. 이 구별 유지하면서 '유효 snapshot의 동일 run 재시도'와 '재확인 새 run' 각각 실제 DB 회귀를 추가하겠습니다. root가 의도한 새instance T0 보존은 유효 runtime 있는 동일 run에 한정하면 확정 계약과 일치합니다. + +root: +> A07 Q2 보충/Q3를 재확인했고 당신의 구별이 맞습니다. 앞선 root의 DB 최초 시각을 모든 새 run에 강제하자는 제안은 철회합니다. schedule.startedAt은 durable 최초 사실 T0, 유효한 동일 run 재시도는 해당 run 시각 보존, A11 재확인 후 명시적 새 run은 T1로 유지하세요. DB T0만으로 active run 자동 복원 금지입니다. owner 교체 자체가 새 run 의도라는 근거가 되지 않도록 실제 explicit start/reconfirmation 경로를 확인하고, 유효 snapshot 동일 run 재시도와 명시적 새 run을 각각 실제 DB 회귀로 구분해주세요. 이 실제 리뷰 질문·답변·철회도 최종 A07 이슈 보충에 남기겠습니다. Flutter 독점 유지하며 검증 계속해주세요. + +implement_a07: +> 시각 구별 반영했습니다: owner 교체만으로 새 run을 만들지 않고 유효 snapshot이면 기존 run 시각/skip events를 재사용하며, requiresConfirmation 또는 fingerprint 불일치면 명시적 시작 시각으로 새 run을 만듭니다. repository 반환 최초 시각은 별도 durableStartedAt receipt로 보존합니다. 실제 DB T0 + runtime T1에서 유효 재시도와 재확인 T2를 분리한 2개 회귀를 추가했습니다. 기존 UI 3개 실패는 로딩 없는/외부 async zone fixture 문제로 수정 후 통과했습니다. + +## 부분 실패 표시의 수명 + +root: +> root 중간 리뷰: _onUpcomingReceived가 같은 active schedule의 stream 갱신에서 ScheduleState.started(...)를 새로 만들므로 hasPendingStartRecovery/isRecoveringStart가 기본 false로 사라질 가능성을 확인해주세요. DB commit 후 marker/snapshot/cancel partial → 다음 unrelated profile/schedule emission에서 pending 배너가 조용히 사라지면 안 됩니다. 같은 run refresh 중 pending 보존/실제 retry 성공 때만 해제 회귀가 필요합니다. 현재 WIP라 이미 수정 중이면 결과만 알려주세요. _runs fingerprint/validated snapshot 변경 방향은 확인했습니다. + +## 실제 UI 검증의 완료 경계 + +root: +> 추가 리뷰: schedule_start_commit_widget_test가 실제 SQLite 대기용 40ms/20ms sleep을 쓰고 있습니다. 고정 sleep은 느린 CI에서 결과 경합하므로 observable DB/event completion latch 또는 명시 condition을 bounded watchdog으로 기다리는 helper로 교체해주세요(tester.runAsync 유지 가능). 제품 _Cancel.barrier 도달을 별도 completer로 관찰해 버튼 disabled/route-exit 순간을 확정하세요. 실패 UI도 실패 Future 완료+frame로 확인. broad pumpAndSettle은 최종 정적 화면 외 사용 최소화. UI 텍스트 신규 오류/partial배너는 KO/EN 200% 렌더/overflow와 캡처도 남겨주세요. + +## 새 메모리 캐시 수명 + +root: +> 추가 작은 검토: use case singleton의 _runs/_projections/_starts/_epochs는 generation이 key라 오래된 결과가 새 run에 섞이지는 않지만 reset/restore 이후 이전 preparation 원문/action snapshot 참조가 계속 남을 수 있습니다. 기존 gate listener 또는 다음 public operation capture 때 이전 generation map entry를 정리하고 실제 진행중 Future owner의 안전한 완료는 유지할 수 있는지 확인해주세요. 별도 coordinator/무한cache 만들지 말고 최소 정리만; A09 전체runtime reset을 선행 완료했다는 주장 없이 A07 새 캐시 수명만 다루면 됩니다. + +## listener 수명과 테스트 관찰 + +root: +> generation listener 추가는 확인했습니다. singleton도 getIt.reset/앱 teardown 시 _operations shared에 listener가 남으면 old repository/usecase 전체를 붙잡으므로 @disposeMethod dispose에서 removeListener 및 cache clear를 연결하고 직접 생성한 테스트도 필요한 tearDown을 맞춰주세요. live native Future는 dispose가 강제취소/owner release하지 않도록 유지. + +## 확대 화면의 기존 overflow와 범위 + +implement_a07: +> KO/EN 200% QA에서 새 오류 확인 화면은 렌더됐지만 기존 AlarmScreen의 graph 고정 높이(190, padding100)와 PreparationStepTile 고정 높이(131)가 각각 72px/22px overflow합니다(/tmp/a07-ui-capture.log). 신규 partial 배너 자체 오류는 아닙니다. A07에서 2개 기존 컴포넌트의 텍스트 스케일 대응 최소 높이만 조정해 전체 화면 캡처까지 끝내는 방향을 권고합니다(새 디자인/전반 U03 재설계 없음). 이 작은 통합 수정으로 진행하겠습니다. 추가 범위 정책을 달리 원하면 알려주세요. + +root: +> 최소 통합 수정 진행하세요. 새 partial 배너가 들어가는 실제 AlarmScreen 200% 사용 가능성은 A07 검증에 필요합니다. 해당 기존 overflow는 U08 선행 보완으로도 교차기록하고 U03(알림 설정 상태)와 혼동하지 마세요. graph/tile 내용 잘림·스크롤/배치 의미를 유지하고 수정 전 overflow 증거와 후 KO/EN 캡처를 보존. broader U08 전체완료 주장은 하지 않습니다. 59/28 focused 및 generation cache 정리 수령했고 최종 검증 기다립니다. + +root: +> root가 /tmp/a07-ui/preparation-error-ko.png를 직접 봤습니다. overflow exception 없는 start error 화면도 맨 아래 파란 버튼 '준비 시작' 레이블 하단이 잘려 보입니다(고정57 height +200%텍스트). 해당 버튼도 minHeight/내용기반 높이로 최소 보완해주세요. 스크린샷 렌더후 육안 확인이 필요합니다. 기존 캐릭터/레이아웃 디자인은 유지. + +root: +> 수정된 preparation-error-ko 및 partial KO/EN 캡처 육안 확인했습니다. 시작 버튼 잘림 해소, 새 오류/partial 배너 문구·재시도 표시 확인. 영어 partial 화면의 상단 시간 안내/단계 시간/skip 한국어는 기존 U05 잔여로 명시하고 이번에 전반 현지화 범위 확장하지 마세요. 200% 단계 카드가 finish 영역 아래 일부 가려 보이므로 실제 목록 스크롤로 skip/다른단계 접근 가능성만 widget 검증 부탁합니다. 배너자체는 화면 안에 들어옵니다. + +implement_a07: +> 추가 검토 반영 완료: getIt @disposeMethod로 shared listener 제거·새 캐시 clear, 진행 중 owner release하지 않는 회귀 통과. DB/취소 completion latch로 고정 sleep 제거. 실제 AlarmScreen 200%에서 KO/EN 모두 목록 스크롤 후 skip 버튼 tap 및 다음 단계 hit-test 통과했습니다. 새 오류/partial 화면 PNG 4개를 /tmp/a07-ui에 다시 생성하고 모두 육안 확인했습니다. 기존 EN 화면의 한국어 시간/skip 문구는 U05 잔여로 남깁니다. 최종 focused 56개 pass, analyzer는 테스트의 hasListeners 보호 멤버 관찰 2개 경고만 명시적 test-only 설명으로 정리 중이며 이후 source freeze/full 1회 시작합니다. + +root: +> 수령했습니다. root도 4캡처 중 KO error와 KO/EN partial을 직접 확인했고 새 문구/버튼 표시 이상없음 확인했습니다. 추가 root 변경 요청은 없습니다. 테스트의 보호멤버 상태 확인은 production API 확장보다 test-only 명시 ignore로 한정 가능하며 그 이유를 주석에 남겨주세요. 최종 freeze/full 진행하세요. + +## ARB 최소 diff + +root: +> rootWIPstat확인: ARB app_en1220줄/app_ko417줄 변경은전체JSON재직렬화로보입니다. 최종freeze전에원본정렬/들여쓰기/비ASCIIescape형식을보존하고신규키부분만최소diff로되돌려주세요(생성trackedl10n은필요한변경유지). 다른제품직접편집하지않고root문서만진행중. + +implement_a07: +> ARB 전체 재직렬화는 원본 git 내용을 복원한 뒤 신규 2키만 추가하여 최소 diff로 정리했습니다. focused 초기 81 pass/3 UI 실패 확인 중입니다. 실제 DB negative 3건은 수정 뒤 통과했고 singleflight 실패·동일 시각 retry, snapshot partial 후 skip 보존, 새 generation 독립 회귀도 통과했습니다. diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-ui-capture.log b/plans/audit-2026-09-23/artifacts/a07/a07-ui-capture.log new file mode 100644 index 00000000..e578eb0b --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-ui-capture.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/capture_preparation_start.dart +00:00 +0: capture preparation start error and recovery at 200 percent +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a07/a07-ui-overflow-before.log b/plans/audit-2026-09-23/artifacts/a07/a07-ui-overflow-before.log new file mode 100644 index 00000000..c1500396 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/a07-ui-overflow-before.log @@ -0,0 +1,94 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/capture_preparation_start.dart +00:00 +0: capture preparation start error and recovery at 200 percent +══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ +The following assertion was thrown during layout: +A RenderFlex overflowed by 72 pixels on the bottom. + +The relevant error-causing widget was: + Column + Column:file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/lib/presentation/alarm/components/alarm_screen_top_section.dart:104:20 + +The overflowing RenderFlex has an orientation of Axis.vertical. +The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and +black striped pattern. This is usually caused by the contents being too big for the RenderFlex. +Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the +RenderFlex to fit within the available space instead of being sized to their natural size. +This is considered an error condition because it indicates that there is content that cannot be +seen. If the content is legitimately bigger than the available space, consider clipping it with a +ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, +like a ListView. +The specific RenderFlex in question is: RenderFlex#fc6a6 relayoutBoundary=up7 OVERFLOWING: + creator: Column ← Padding ← Stack ← SizedBox ← _AlarmGraphSection ← Column ← AlarmScreenTopSection ← + Column ← Stack ← KeyedSubtree-[GlobalKey#9ee88] ← _BodyBuilder ← MediaQuery ← ⋯ + parentData: offset=Offset(0.0, 100.0) (can use size) + constraints: BoxConstraints(0.0<=w<=430.0, 0.0<=h<=90.0) + size: Size(210.5, 90.0) + direction: vertical + mainAxisAlignment: start + mainAxisSize: min + crossAxisAlignment: center + verticalDirection: down + spacing: 0.0 +◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤ +════════════════════════════════════════════════════════════════════════════════════════════════════ +══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ +The following assertion was thrown during layout: +A RenderFlex overflowed by 22 pixels on the bottom. + +The relevant error-causing widget was: + Column + Column:file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/lib/presentation/alarm/components/preparation_step_tile.dart:98:22 + +The overflowing RenderFlex has an orientation of Axis.vertical. +The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and +black striped pattern. This is usually caused by the contents being too big for the RenderFlex. +Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the +RenderFlex to fit within the available space instead of being sized to their natural size. +This is considered an error condition because it indicates that there is content that cannot be +seen. If the content is legitimately bigger than the available space, consider clipping it with a +ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, +like a ListView. +The specific RenderFlex in question is: RenderFlex#d3100 OVERFLOWING: + creator: Column ← Padding ← Padding ← DecoratedBox ← ConstrainedBox ← Container ← _AnimatedSize ← + AnimatedSize ← Column ← PreparationStepTile-[GlobalKey#47ea0] ← RepaintBoundary ← IndexedSemantics + ← ⋯ + parentData: offset=Offset(12.0, 12.0) (can use size) + constraints: BoxConstraints(w=301.0, h=107.0) + size: Size(301.0, 107.0) + direction: vertical + mainAxisAlignment: start + mainAxisSize: max + crossAxisAlignment: center + verticalDirection: down + spacing: 0.0 +◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤ +════════════════════════════════════════════════════════════════════════════════════════════════════ +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +The following TestFailure was thrown running a test: +Expected: null + Actual: 'Multiple exceptions (2) were detected during the running of the current test, and at +least one was unexpected.' + +When the exception was thrown, this was the stack: +#4 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/capture_preparation_start.dart:37:5) + +#5 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:15) + +#6 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1952:5) + + +(elided one frame from package:stack_trace) + +This was caught by the test expectation on the following line: + file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/capture_preparation_start.dart line 37 +The test description was: + capture preparation start error and recovery at 200 percent +════════════════════════════════════════════════════════════════════════════════════════════════════ +00:00 +0 -1: capture preparation start error and recovery at 200 percent [E] + Test failed. See exception logs above. + The test description was: capture preparation start error and recovery at 200 percent + +00:00 +0 -1: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/capture_preparation_start.dart: capture preparation start error and recovery at 200 percent diff --git a/plans/audit-2026-09-23/artifacts/a07/preparation-error-en.png b/plans/audit-2026-09-23/artifacts/a07/preparation-error-en.png new file mode 100644 index 0000000000000000000000000000000000000000..19f2d9e6596c1a6514964fcceecf335e80baf281 GIT binary patch literal 148794 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

?txmlkNv|3R~rFX_kOpz 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˼n!d=534p66@i9*(F~_;_zD{VyoB4BlxxOscg&TDmmhh!AZ)AfqL2wXrI4ZI8#GHv zaaF)~dp$xIGVWHaHNUE{*dFs;h}LF)`Re@|N4u5<|8N{Cz*O>FUJ#PSkRe%ksXOYf z>LgNs5DuQ4%blz4sIq}F^Sa(#EaFTR8g^Z^xHmD6i!CD7(~kQ4`#m*O!a>Y{;NjJL zzHTSEcX$Y~nh!x2!5s}OXNcc_^|N2%kR1Ky-U_n%5^KG0)Hl-8aIuz|QmLJmEypEQ z*!EFZs!AbKITuPps(r?=N1{H*awN!@lG&EKV2h~>n~3+jhgkQh;vs%(5Gw1tF(PfAkcqPzf$wLEb>THzA~K(4mqhlT5)Nt{HSf3U`-Stx^T6LUOzBg72+32 zhg6uzv&&zB>p zM`w%hjwem4C(WKycC zQBXa@Tma?hm>9$1Omb*{XREIP?IPl`zE^cou{N!K>Eg+6IdK!r%>`v;X%5GKcSpg4 zC1h*M0WrhK_ZVPx#OCJaa8Dw&r8v_9AyM)pS-s32?kJv8e>l*$SaLPdUAL;FX^6>X zD0)>yYL>s-Eu0@^#jSK}3wmnVAonWlP-p*p0y=Myn;3g_eZ5e>R|AP&l^82#Xlj;x zy)+i~yTEZBmmSs=!@s}#)Oq0=L@WH~*@}5H5En?{LavgQsWNj?5K;tF$p~!mM7{)t zFvzjqr0kKejuZquBdA>lVC8QmUkYrRj*u7G) zH@WyDZ6mFNM!`7Qi76pf1U_SVkZ_B`K@%!y>vj(v#4SWQ0FJ75A^i%dLd7RN$}~xv zPZLUk05gqu`LOOuk*n9*hZ(c0**(UWun@(EA9g&$b@wf~Sz(gXW@!9YDr$4F$1o(( zR}=(pOZ_-Y+@B~~%+;eF+}rVcxN$U)(8_US61&^HtzIN7AHkwc53sBE>?F0@J%Sho z2{rGR(1lt;zXt_RJ)rw985t9x?1qNN8$cSIhlie4tqNKnb7v%idyQQ>MmIg7ln7&} z`d|=VEcgZW#*K7^91K!^X8?|i0NC9VKLuItkE(Oo5jx@^qsj*@)aoUV0c_Zuj6BIT zkJZQPhhqi{AwsUEhm%xdbjCtBlGRXX*ZBV8lK%ldu7J)WS|Md(M*9Xk3KFe}agc7- z92^`@c$-9c5lDGgxFr>zKwnT(lazshL8E%7e)^ICNMF18qh6E|z>N%tt0{Q7*eRrQ zhlR{LE_B7C|l{<1@MY`Lu0b1=A5`xu2p$FW20J%3C^hu3_`=E7^wma$~mhN@I zChIXX6izEWT=sd4oE>7n1SFLKHlKd5-Dv{lZ3H9DLb zoF?QQ%3|FA;RE0M>2eikbRM8z zF64RC`Nx0AD8;3b3x|iKjg*1RfQsba5CAST01fe=#yVGT0i~SZAtQiXz<4zGBT%W~ z5fOk?+4=bR1hQyoS^u31fHTi%DM4EK+29wD_t*geq{#9HczaY=^{>@ku|+T6c8fy5 zho0=#(blw}k}ZckP3VWy3x3cW41d^b+O@-ZRh64$x4XxRI9wmmy>Rm!8yj=J{2DHu zS@+YgW(P;->;mmB;GjB&Hm9w*B z0q*tRV4G+Qp~VA9qipgR*XQ-;U<7K+t!G$E9Rn*!nrRv1Zu7{dGBfArv#FJ41?%UG zG70w{V#v(hyVN*TlvCO)xi`Ghe}C71u~*X-IEn?wBOcJUEncKdvKXAANykw;_zfjd zG8jzRQ!!}Nwsg>N1D&CJ82R~{rZmyh^IW4pAX8k0|1|}1Tk_*`#YaCqf26-0u6YH9 zQl%n2;`8HccZee5id(71ac}?gaHBrfm91FXmlwa~acTav*7@u1XqE!qtpb-TsK$&NZ}FTT)AZvy#N<7>yFqXpdTHtb>c=rxia#-kB{Hh;EtKDRB!`Y_GIWMh`4Q{ zp@Yqgq0E3Egc9@)!V0JV5r4`0!KCdK$OM_0allC_RdrNQ#Kb6b?_px(czd$1f}T@b z_T7ljPh}N`H6sqS@(9xTdXtkXuQi+DUX=0ix@&}dmO}I32CL-6oyN7P&qB3X5Ev(H zh<#mN-D~!GxNx#78%(dNaQN}SLH;j(pVzrYL~$K&xB^96c3LI-GAg4?=Kcm?q?QM( zmPp%0k9xJx+o?_*yL&^w}z9=iVEzPE8|hl;HYl1U#6TnHd_$RshPsY=1+? zc5a1rvOPPX2BH%YFA-4)1a2Hy?~E3!$XlX0i7}dGiJ`SwLdM))O^9$2|xK1b< zdwAkbulhyX&@kid$ytq&g2={7a?KN(X#7{SMMea3u-%}f!a_Kd^)w{db`UEOLqphN z`U8sDK-hhSHaY7b3=;WYb&1IDhRew$WC~#2200LT+SH(d(2}WxS>0G?n~V>foSayIJjle9ZrJ|F zcU!eXVcRbt;6B*+8=9ABD5Z#2rYefu+{qn1<-gy;0vhd!I|j9b?KVJ?;9;T@7QUs% zS3)6K!hQXymEHp~Bi1;5k>$jiqhr=pc>23xlMo6a_YA$JVkSoC3l26Di{~C8H`DIY zzh=+!)0;iSs<|bx098jv7(;JQOacGxpBP`eZbdrDz_KS~rf_8z0y#QW=i+Y^LOhoL zsakI~MC#=1=KF#pbH|Glbk||;>sB|zEW5|JUQtn1|HLdZpLPoFtqnomMFPK08}>$c zstD#~bF>d&V^dOILi){t=_Wz|bUIUQC;93X0z~uR;hoOHYi=APl+}zxt z7V00Cul;?azSvL}<;2P&&059;X8!+r%Phw%xiMmTp;+)R^;8Q}u1KUm;1va6-woh6 zF8{po2q!I1`zUyp^}6<2saEf9T6A6jh!GTW)p9DsiJBj!V~E2E$9PR!gyQG-I9)OC zg+9ZSPOqj^u~F(2ovD%TM>EKkWUIUew-BfzU4qhz9)RTn-_bWJ{o^_Ynn2%QpyC=x zfPaNo=K)>6Vo!FfSa(tMRBTP6&B7HDtX%REFI0ODlrbZq5hk+(^>+OE=!`If&bvtaB8L#Woq(OoXaRMjz1U%HFtVhQ8MuxS1m{1*IBvEAdhC9`HdQVj7>F&cyt< zHUZ|6)B0a@)LZOrR5+2}^XoEGi#=l|)iFe6C?(1q;RAM)|#X=U8ANF5X*Z4mjAv3SS{W< zIyqI?*q}aIUMF66Sc!csIf<3_;rBNperKj}rqtl_mZ#v1(Az335Nmx~`0JMi&B&GS zpI zKF)nS#o@Bc1z=Vhs-F<-*?p@D`}@n>2?1@RueEto0R;G3$4e!Ip6aYX3i8 zLSo{^`Z+5c3;mX$PBw1V%+MB)+W_hEj?3?!02NXdHgI2GUmrGtRAXhxgOUuFE2iNH zWipY}%W-6N@Q6Xh6~Nb9$HrOKM7|uhi#*$8&p@X|4v1laLqwo$&qy64PP*C;(s%Ak zljhEp=N0e((uw%+r~d1`$*%ztga)rk_XEOLEhr>oIuAEI`Rh-+DjuJlQ<~g*nY1=i z@D#BzLT=)j3SE^xvg7kJnMgKz(N`p{T@rV=b1*mM{uq~DCD`jBExBN2@KU)m>+9`r zYXh|?(CviyCEd5YiR`N6@Ddw?obxTSk%~rxKG)~|%z6W2zKiHsSo+7#l!bcTV*AUP z@< zhXdU$2WJe0i-xM$qcX+w(;RMp)V>_mV;#}3LKu^(RPc!$3iY*P)lLS!yO{nT;3H>F zYUQ76YVp{ux5qe`E%wAjfqX%rOT_mDIp}Pu0s;ob9@74w79ij)$;w($SoMK@(4r-H z4KQE9*pzlCNH;eX9&qEdINr#Kv%@3mNtbb<;3TUb$1G!ElCmGl)ip%`qm*uq`~N@b zV|Ft{yna}!r@b?1PkK{BD+>qe__> z>MIF}hDh$`P0^f@rYp{jp3@j{bvw)LveFp1RImCIg;)po_?F333eec{H1o!tgdUfX z^sm}xsEdkneEhiWVyg)f53h&qy`YrX-LsE)HV*lP^2cDW5-& zHj?kq)_O3yuF1@-CgMl54BlWu0@@xBuW+m_2Cl+zva83gkLb0+z8h(=G1zR604sM2 z;K@Mid6*)fv!Ril(fdQ9mmy-G?F$_02Jdd04!mOR{0xJMaYaEtnh}KDm8c?q=@_XL zSTtFeo9d%!<$R99z-zQa4+@Dz*;pYfwCJ<%R-g?sP)}g5fO`Z1xMq?01|2m1@+@gw zJ(`tRuR=11=kp8=SN*r4STNLyzE-K=+wI(Zp;n9e3P@qC<4p3)sT}-2Ai3k<%9Y+z z(k%KgexyUd&T0hm^0{p?F#IuYRr=1IVs4wysOBkfBBG<6)uN&Hv?oUv;;u#e1OOMy zrs;9Q;#Xf%A=E3aC4cBpZ*&n*@@U%k=gOOQN%)pqoU)l#`TA*I>6pd(PDiEfLS37Z zlu2&Zl4kE)d95~7|GE_g1*O_Ck*7IB+(@yxya%&Km<*V7)N*Ij?7ur_tCekM9jmP5*Vn#!0V=rk0w*hA`bPdS zeJd*~t$=A*P$f9)H-w*EZ_U;vcUE&wJ zzwbA6oRz>%;_SkxAa-*;gjQnXYgEbSU0q$kYu|=+e&dz6IBmOj3oJ3&@n3%V^9sOc z&_P3{QexDsH_*qat)dL+mJ3GsH8luaGOs%~_45Df>+_L)r?is)0RU;b81@^0}lH3`6JPAV&0bofshaMu}_b2VDQEx%I zQLD5L$P5wN+ttQJ{n2x}xxa>k_I`C#6Sp%=>BXo#8oFZ6S??zqdMq2N;u^Q z$Lu7gThB|?ul=zmhs-IE8Zbl%ZPwA1!D76&tHeap8+VvzBMAhDbgN$s3-jN)?8)HW zi8%I5MNfF?vT&4;5Zi0-ryMcoj-`1Dtn$B?HaD~1i9xsuMQ&kB(2k?Vn@}HO<`&_Z z-FR1HkL9}}W>{ro_I2!%-07C&ic=H6zSwY)2-w5hxBk8Hq!?_Bk+b%3E{n+ZK4kZ0uLBYoVcy^s?o!7tot-7uU$qDiB-dn7mMU+h4a25Xh zI4LS;il9yHiK%J>k`eUWZ})co_G%hF(U};|Qf0O=jr{6afI>U+H$}~t1pKOqKkueu zLAL`UsRU~Ypr9;~x%7v-yZbJL6(X<6tZD44vm7)xH#gZDm5I3NO3MYV8NNJ578aca zm(T+G*h@6!&n#7PFUXs_PjFwT{l`)ry`u_HPA3ps2C(^W{JpA`ERa-w0G?DLbJ98& zug-A)x6api#Y{;d%8-7as<2+gx%e#(QB!(7Qg9(%taCcqxu2(4ONbH42IO{Ifs;4b z1Xm8D5Shc<-lMddVxjq{f41v};|~8%qCW5v4{!KEDwF3}-RGlk>k^rStj0z>ielU_ zUBhP%tQu6-U@mf>f%>gMu1-yQq)EBMf!TI0e$73x?TSJ33NAT6Ff!SHmn_ly!<&!#@5g84I?et8g9)k2odi$|6Ry#$EXVGY?jNfygi5#cER&yZ&E6wM z@85nJg#BfkKUqF2^k8%Rdz$p{cIJturGBcewL+*~wgwZnE0oZBJ+rr6{d>!LH$fkJ zAcm~l^2;w(*B_~YlgrZS#0RhpZ6l*diM97VK;u2Jk@f==8c{@foqNR8IkfD3soGx9 z^kM|c(r`9Y8jxdu+@9ejd^|eJ@bdTZ)$ut+D}SO^qQH|NHU^Ee0nqqBmk-iHMOL;e z+2b(6$Fw-3ySu6;N$@P z2FF9Oc#Tx?yjO+% zLZ2uFUy=#GpfrYiL_`uWabOL`up^t7|F?ik%SZpYYGN+FKOf%@+kVAmh;}z0d_)>G zdx?&xpH?^480`(XQ^*Y6%KZ5ekO$qv4SdVK&lc&kTP=6Bw6=;Qd5~O>3WaeGWgRX^ zg~KcQpnsfa78SuVwE29Q46>kdi9J22qyAi;A4d`#Ki06X`90FYq&yp-fOHX?$+-yo z#CqoDz5q_*pPU1i5{b(sRaeUup^PZ3GxS?JrK?mf{rMBWt0oq~sC^63=wbc4?VaWt z2jScnj;o&^Ht?!VZ;W$Aks=t_QFn;X(DF31R91}>>Cej4s!soS9K-QOlIujA+46W= zfiv~bdXhYj)87>^Av-P@xuw0}+!{C?C^kM`r)F8oqUTl#4_AICt?*Z`g!mh6$ApB0tbe=3jaz$Q-#l4{lKD~g&-u&+_kzv5RfRLD zgU5*VCTHxh-?`_{6T~b3+-?*OAZr`Y0doFdK3n?dl2*!4v}pePt*A`;+#z}%0y&{8 zwYCG0dfIZ#d;vfrpgyV1DWw4T+t4K2ZRcY2_0a4*v8Kh+&zi)FZ=F*-x{q1Qb^o*w zfID7ES#JSSb!zYQtAI~i%5`f`B-cYw{xo?mh>1WH!bM_-hQVcJMsE`&u`x?xk<5$M zWLg>;Fmy}MFbw6&_<=VQ#24DN;k*47LDnFl^z}RDHJ`0LpW~gmKCO+6_AC^g`?k>? z<%;X1X%tAQh#l>p`(deZ7!x-NGc--{JnrfFBo%S**+Kx4=cX~cs~9`Q z>1?}Q$19Hi#`)n>e5YYK&lNGM9UlJ-cS&vINd}+fk z*`L@P_*A}w%X;-mZybM`;6~+QaXUyDe7$JP3-$eD<_0YAegh_IXkATex3qOU-Fwa5 z=uUC0VtDD6W7?y$)pTOMJu8yh<}mQhH;dwfo}L5<=1-QwG%YNs+-qyU4ivx<{MQ2z z!em3(0_Fwn3C;m?OIzM>766xR52PoD0r>Y^XC2Dkb9uGYU}4;<9~ydo+|L!4jKx0}wmE zl9R)YVk7T8)rYz+@?QWyxN_n^fR})OV`aMP9mv`qSm>|YfQ|&IUF=L{%~!A@RaLpi zE*{95rPn*-6*M4ngU4m=a=Mr7+10rdEB9l#GC>BGnd~n=)l23;&{H7=N5Iy^gP!SH ze>-;}v+|n5uTFyN=Ib}OGH>!xAL2OJl=Z~sL|Ls{j|@r16je=*s~4f-aKHQg;rao4-H&=!uk6O5ezc!dkTE!uIu{2!{5MnnehM zrb^1bQ)ki2?BA~N80+mRx6p1iPM0epke&?xQ>!QROHJ)y zpKKFd?ukLI&qpz3B^EN}<9Q!m-Vj3O-;vi3lJ}6_Q}U<3=QJ;QDA=&4ule{Q1c$F3 zI^M4ItbM7=tCRVlCo(Z%cj|EV4&i)Z>6VHVWmFe~MLFE0Wt zkqY9u2No+mrc={-{qY*~o8IFFTz(U8u8)ax-O3E3=;pAhy}y0A|Ge5;;^LvC?|KN8 z(n}|W+k%a?GZ;J+6pyz2XB5Skyfhjf+v2@#sJi`NMkrv1k8nmr&`-@8MhAYjN)>pA zaYWMT7fs9AU*SXs(m_yW4ZIS(9l(q1GAM$J8+1R!rwwtK64N7K7}QW8EiY(|30!BU zBMrE`RGM38j&cWvhuz)2bYMA?Mk%QY(Y7qNp;jC`2}9xhv_^R~KrSMs{^Rbo3@`hR zO9NSA-I<+)2~lNb23`4sncr2nZAs&7^UfCAbYJYYj;O9BSXjw&;OpV`t#?@hw-_CwA3l9D|dRw)rx8H?s7ot|&t zGXnXdjh3#wt5FX*E3rt6w@UL2+#b z*N1Fwi6fn<@~zizOr}OBlUrL_T&9}H$hWg={K@CkljljS2wwH{ULW5hB=iakqlBg) z1?0IPVasrm&RuP4VVjqQ*&SUcPA_F-WMIT%5ORs#CUj?3yxiJa0*`PyBE&1=B32D( zYk#%0==Css2ZaYrhx(C#I=HP)B-tJ#zS~+98XB6IeiC_H@cenTZ1(EKN`74(zZqW7 zr@vDK;8K@(XYum&>!(f4%~|Xh`c18Mr|T<~Todib**hWMR@H;&iSwelNMjB`?+0yT zn^2h*Om+w!&HIk-NF05%ld+R?-u-udIJfHyR+iPtn6gwHpCrupV?m0lhlG)DwLpzC zgtol+fnpQW2F7S8y;m#xWLb8`8kJEP?JjQ>44x?7cmCKo^CjRH8T;Z81sXE8?Jiu; z+syI6t8AWZ-A)>$r{9yA%Dty|>eH@o*MHahVq%lDucF!6kDDLwx@gKkk97kh1g%HSBqMRitdebO{gq( z>c46JgpCwG{pE}U`-9AXwb*m(hiGr!yt#k9jI};?_f&oUJaE3#nPA4ZvGqm1?XCcH zM)#EeS^otp9w^?>k}Y_jkd+lgJsY)i;MfJIf#JI{u4O&avJ!u9Z~gPXI0lAGDL_>a zcBYF}SkYEKfYExPMAz5JQ|I{2ICB*TD7FxnL8YfVS(@`k$jMf*BSj2pKXjOK*m2`NuC}N<{iTTVx}7#F<37Pd$!WdsN?c#M^rK=xBISL;ey#pc z40$SOBQ+L>-qvcev$I1QZv@#zP%j&dxp^yy2Lszh4vloF)!LYv$8TtQSR6HA^=MW% zPoHa?)ZP~T`yfJPol2#MaPpY~OQvQ>QY!;e|IiWqcD6eMN|l=b9coJddH(5iswKrt z43&p}3RZA{!$8V~1-TB#$}PzdF%uQe)>Ey=4{L5o{oeOvK%~TY&3(wb`Vu%qpONn8 z?~gX!P_1g$^;aLm7vin}GPID6u;gM{#=XU!<^tI;yYppY>DaUW{H1rP zlK7(V0{IDpm{+dzEXb({KR$m1SK$o#dj4L3AHwnjf))alqr?lm7At@ zt%yK3RUJt&?W}BXZD=5?uvlZ8{6f0YkTNO8|Lo*%46YhAW4ViiA4y#k*h!TtK85@{ zqALF{-ksgQe|p$s9w%EF%4UKgDdrJIXq?R z{$iQWk!+dRp6Vyd^fMg+3wU-9cjkDhvu-S#sBuOl3Sm%?K0ddSh5-U~O{?gb`0Tpq z45;7mIg2-3{<5>1&t8;WI$;Zu`84$eLl^OcO0*!t;(Mk1+DryxNv4b1z0AOX1g++5 z*fiiIutHAFP^)t0he5{XDSyNQn53F5GT#3HUBTXBJC4^Q`ph*^u=ai$NO|{Df{LD2 zErs}@rU5uD1?aE(^2AJy~X^rV(i8V>#nzx!Y|se#6R}?6nq8E z6)4WmwFeucVIVl)+Z>nWDlu;Qy5&-I99L&Bni;F&opY^kq}8@)d0F9TgvDFMb~C;_ z2Ft8f(OJ&qQZeaF{`AYNK%|o*sza4ODQ#EC#x)6bSAaJN8{XGBtGD=^x`TBHpDP|U zOgiUd^S1&c`cl5u5X|f>obVC9Ppqg+C zBw-g<|DAG@xG`s`xl}Qv3w(=iA%`X45K>tfoJ$gwU`k z!_0+pdVEtAUWx6X`YEq53jCri_tf(`OF zB3}DoSkwHX5{jMAw^4<=1jNOoKmt?rWt#-d;o&>tcO^ySGparPasQ4L#6+@Y71{1S zGr@THVSjyA`Q=919a)p4QuPsDx!{ry+Q1gN4nQ1)@ zWs#k^&sr`dQ|)#Ce_Ako8LXU4TJI=(NR++ALwttYyIZAqw?9yeRIzl+RwR|u_n!d)@DI8h3czf1E&yaF>v%VlbIEM{A3O^fUtjVV7HDGXUB-`Qx}^^WGSI!w|o zF_wb?_Ys4HVt^H_llkLf1fZ|2t$j2wXzS+omu0PW%9_~Zw)HZdyh;%E^{Gi4p8M{A zVlYz9)!5p#&@+t&AY|;HKTnRfXFY;~$ZD!|YIq$%izld~!}2@fezB2)Y6jcNTKl#T z;`!g*kVgwHYrc>Zi}#jOtj>Sn6Ru`ZeLQ=gdg*L)uqS^H^~W{(D-?0&<*QRNd77|9 zi>Klkc!f>f-M)}}VL@4lR#=lAS_T;i`(Z9QW)cU&xNN5>GCYIz-L=F~f^J&0?Bi;a?F=j%XFZD?1VqmYww~-(MtL) z1Z2|V6aVZTV)j|%26$$xv0$T}KWneMw(KIBf52YL=Wyh6D+tDYtpD#r4*m|0#I(xt zw#LccLcBB?yBD(bo}3|%vfX92#F*V^B)d7BF$pD{*EIiDkmqyvY#hb%3pk_6(YbzX zoy-10(%X$yD*S%2g7>3t@WAaV@$sXC-5~S$C&G>h20aI;oPn&kXR|FmO~!%D4{+xm z4?G1-lgZ@KR)O7zx-0*N}~)G<(0d+UM^o<hE^Jmm{ma*#@ zVgdmsh!+t)_z5R(Pr7EAmlyoU$LniNQO5F@ex)Y;lrU%ruf=6hSWMF9wCWhedHQDL zsY@byK>OiVmMyNu;g6xb7x?(Ag{E7$Cf-U#cKdKg;A&{XOhP#GiaAjJs{^(7dqSdF zh(#_)y)RP16cIN1Iw8Xuc9Z$OzO!v{KKKVg=5N3PvfcFeegH!KC+UGy$wDIzGuOyC zIKsUt6=doM=!UJ_-9`2m!tJ7YtZsr%7ouPkOe9Zd$t$d`smYLOyeW$#C`GD4_wxW7hGdBw=N~ZO^K8g`S!kPjB_5-6_paSc*3yty-bI_~PDToP;a!FxJ3+b+Fq&K<=8T*FV5@12lsnC?wj>JsrQsO{Vtv%S zzT9#lzlb*VTXF|Pe^fIwvkO*Je9#b_&)fO))G+pqO3MDG9#|ey{sQqLIop+7ax0{CVbQ0+Efh1+tH}|xm=RzF?zXXU2`yX-C&VE!~Y%yxvX@F zHd~QAPG`YnRVz-#b&^JQJoYUovtggMD(9#3;raoj11&pe&^&1lRP=pCE62f>G{^wU zf3^u36$Yu19!HUs&+a^-<9PsIUyZARYkJS~0Ouib=LNayc=Z<5{eXUI!XR2jDVXGP z_tp8xVK_%B17N8}QZ%(Xy2l~BwXS3+U<44RZ60L(9&uMp&jiT7!Vuvfz6GL z+}uJ_<(A)J=#Ok)$G66+<@C+|Dp~#M2v&2lV_sYbElzI51eg~CBRDXeE*V@b-@60Z zw6i(R9NI%HDPh8MOv3Z~G8K*8jQ3)dM^=`MCe$ikW2?j3C~aKiiTgS+kNsEl!BCjv z6`E=nE%r#aGcsdODf5;Zh?gM_!e;Ra!EBwAD_q6vz0_^MfPw7wV+D0g79U#HqiKKw zeF{LGavMzig%K^ptm+*_ADf?t^f+A{$d8!wwT)x42l~tBsDuEAE>omO4;Q(FEgPSl zoxzO_oq*Af!Thb!yegNY>OgW6YRwUluZg`+FhPd{ka9ZlYyln9e{scf|MYlKH>7(! z)t%Pmn(^69eGK_14pY0m5tPPTHj8Ob)%hG|vRpr8mLE0r_5BhL%T2#TWclQVr8tD@prSrF=V7|Bp*amjCo)p48kH<;%^=5{GFmmY)*j)Lj2h3-GC- zz1>s5-6W7-TvYT47X*kls>H-+o?c!^KOJZ{{%mY~FDttXRbgCB?02X8M8qbR`3w@& zBHSIJG=8zMejVH5I&@XXaeC`r!~fk(GnL=4!rqP)Z+}o|SxP~~;TU^g`)V14qA=-h zAVXa+l08?dg6W7fhF$wFKb>+x#H@4Ft2*aP)=)*rhDh6CVK^XnFggbZQJNbgJlv5o zWRtVP$6#6I7=;;TW_j*MIa8Z4naxsHLCrSu2$$-E@SH&C*tm*pp<80xgK_DwnYuZf z)6$9MYeNIPA6TpDrKioKF`OC#1SV*Vq`wF0HUc z!XpeI>5js9K@V)k53gJ#e0({ojj==5h7#zxC_>Lkq${j?z4P;b-X&dZ`0lsE1rKN!;^)Z6~z)W(Q_nY^gqn9 zMfyEwO0OS%I#qr7vp)Xax9^QWl{VAGG2W!xlSzpFE|#}++7#w2n6`T`j)?$kRRr(3 zIZ)mxKYe|hXEb=DOrxSAEDpO=O1fgEeIm+#h2?!?WE*H5YeQ&jqx#%AI$#H0G%SZM?uj|b$Ra8o6_k;`e1E!b!TU8!Mc_iNaL8PNbDzW@A5$N z6rX2a57cG^qdxH$Ql7c(wZ^`Ij;u9E}mIQSkH!i2vZuH<%P$J4J5+?lOr; z7RKS@BXTGE;y-2mPI7$aHV+>@6f{u!FX3Z=o4d$^2ZXPclyvqAlIwoBxh-rQV_Yk~ z){GVx7x#vHZ$9cP2E&C_vT(!1=B5{1O8eWoh7ul~9I1;JMp_s79PvRXTi@5``qfEw zuu9cRd8;E;uY0mBacPqElc|#vDv10Y;sbz+SZarA=?c&Z{a=6 zr*2Hhv-WtR%$xzNNfb|>xZViWyI@Ji+)^DKw5AL2V>9RP$P0RuWl{^%)j;}n4*?|$ z$c2B;)c&WUqJn?{@V-!-__yHF=rqDr0N|iHnXhIwDhF~lr!X3UC!L%6ipw>>;*#Z)1*+zXqVK1Eg!G4Mib&cO}i4+;hySwJMa zqB*~_8Y`fFfKArVe~3(Tk$x0IbT3|zgq=B|W1|boU!z%k{;WG)*;}S^7ZWqd+Ayux zIdq6>P8@`^O_}nv@;DDHXq1D$b1$cx@CDTJknoDXXn3Jn)e3AK-`P}G7m@x-2M4#} z@8%RPZ0tWxZIP;xys`eaz^4ZPt8oSigBwbt6D!-P?R1dKg7FO1! zCvdE<7R8aXj-KrIM!;yYrKDcHp6K6zILL+e_r3C;3}efJ*TXn{Y}@hh*a5<_?>?US zbJ8*}qk(}0Y7sFKUR;4>TI7?F+JSCewbVop2AIL<(TE)cv_ur3nM-B6B>DdAl zCGZ6m={Z&114xPzTwH)tb@~twlgyBLsm*Ppk>4xZP)2E5#V+9_9*AHGZrP9jbr-lAGE@JRZeQ(Uena6c{7C}OmG z>)FVNJ0IuOelN^&+KMyxV`pOnWwf5jM6pat#V5#=3Nt*>+@(ZT%xgwr>Nh>ytWLro zqxnUzfE5!FY)#b@|GLp&JhLQq0sU#=OXT~U#eMu3>A?dy2Lntgi(od7vyMj1r}^;p z^K0>ggW~@Fp6`A(8rIgueZ&32BHRoCGK_D8TJI4MGRq<;0 zT3S-M^k?7=ab06)HQbhZg}%so73m`?Dq^L3_uLNFWPzW=&hgV_s}PrxgARQ?=7bL( z#Hf4tfg{|9q$FW-^23L0uMzv^YQ`fA+i{^W8)sZjEOiHoipTYEf{af_|HsG}l$|Y^ znwkm~-~!OS7@Jh3K|J^2aO)mlxjjoL-e8KHhlaB9O_EFg<{j?3>$rH&VOs|~jMu~2 zdR%0(IN_K8!}>}(PuG?&gVu|5{G#eboUM^FYRY=BVOC7GLm@BSnB;~@SgOV%aa<@h zM|jUA*gm+Wd6Sawk@nClWkxWfLVL8~S*>yI9;U>FetF}1+L*4I zM)8%{FnpGv>+G1V%jBh({k_M`J@m?KBI1^~3gelGV~*cm-f8W9C}_06O_1~LQXhyq z)rMe<9gDWJ4{%T^v}6Eq_s7ae6Q{x6ESTP7qQP;szWfn~ddubZ0{`;O2*;UMKO#Bl zwq}~LoWqD-KA4t(=TN`SD-;kEO3AZ%3-VlB+ZrfYgtv>(|JFoFPyf~D+ihJ;tb4Z~ zyq^vBVeQSzO9O^K<;N4Z>d`{6<5HSz)fxgfH-S|*J!Bc`+r!LW?UwcA+X;zz(0#uJ z)GrGxk^)|eLa8iP3sX)W@PSD&;KzP~Ji1FnE(L*B<2Cl!U>!f@Fqys@FwuL$>>tC% zz3co}Msp%=&qBn_>AYMiwG-{pPAIPa^tL>*=^L~w@=3?r-IK_yK3$KuUNsz{%6W=$ z=8-arP*%aji1-H?6c)4JmF${UpIw|CYczV$Kq1vHNkl2Og0@tontnv6Ed{QYFG>vB z&<^=v9cplr{*1UMTBks>DQwmxf6(C*7Zipj$7gzWg-hwH^i;PBXZMn2#|D7H4#G;b zV^td%j|&TQ)V#$c9(7KUI+s@0$h@r6hm}H4ls+GssE3iyX1Qa*uSQr5rb~U{B7@n# zkh;3M?Rl*rSW=^%Z@?~w-EKh)M*hvifmchqS;Kytiu%EmRsJ4sNXMG@#+$|QM=U2D zSPuiFu!!9EhV4LoC|rz~u$XIc`t(q{+94RQ4Lk~AY51<;Y$46e%mn7r+rv%I-lV4H z+p_+MOCHjq=RtZAoqlJ`I2|H)o`AlepDYYLgCAIye? zFUr1yaQ!Ow0eCJ_f*VYd$J9*YgAkJ8xjMIHdJWO@)502fid%CzzP>NeE*GOkv)Kn0 z|F;bhbK1_KspH>%W!%eFDocfF&3_ymaaX7>md@I=;zD;cYV4O6HtL+eKGyH5j}?LX z`2_&x#4r#{d6hR05qh8yEQctf$@bb?W2_#_ow4$w7xq$<^G|bwh3J7n}q0otv((^IY`Jdrzy^II(W> z3UQHP`hdsg_U5!8*t{LPs`J)5Mo3hb)qw2w+! z-8l+Vkh7~3b+DZD`Nho$G_v+DCSISbb8Ovyexc*tnr)(^qQpy_j{}e-kJb9d1_n}~a)IRFC~~1jI#B4K9Rl~NNnm#tb#Wn}37SmK z%KOE$i*-^9YIvBUPF1^aT|t{{n-8mJjDhgoy0jU+TZ5kygi8U(AXqtbP#G$j6xkh|-LjtDRbHKXBXd7Xf0~iYTH%(U)`%AaQEXV19!>@E z0i3D6_9&Lurlv8uuILvHUX5x;nQbqF^%X}zK}l3GyH%n_)^k{Uew~W}c>yZ53^&L9 z#^Bv|kE=7P6@G(~wfe33EjPDZ&w7dUrr$B@u&M2g4`KJw2}4yu^MqMSp?+XqCNCa z4){X_yriV7Xe99kFa~&)83t|T&fG$_uSLAiwy}kUXJVE?gEH>Lbt=crr09Lo5So$a z^s3h3Av}%r2!6lYvC;oKQPN&;*Ee`p!|?83ODKU8E~Q<+_6(Jj2B!J&igPl?aor7{FC1 zBy3AY)A53)C*FJVewqI3*~-@Tb`HaK3dSx*-%gKz&+>+ijSbKvKBRqMUj8|50bD1l zdp~Wgn-6v3@7=zgT&hxd8TZ5$w=25n5iM<$wI`kJ-`{>*J-jfW0{2lc^Ss~KD9qT! z;M=MF@9zbJ!WJP(;sV}sH#f(KhKqkjrbE6jI67WuW@h#x7K{d%NJ%I%s8P1XFL z%sREoehjbzfv^HN6~Tdl`an2%K(abi>q>@3YK;tzdt`r)=3EEC0l1LqFpq&>91l!{ zX<1m3bgyRJCMJqqt7+UC{EC6OQwFU)=;f5-lyJiN(Uq1$9lTg*_oZmO5TG;RgkL#6 z!K8QeLT>h48LxLOi@W;_oZi4RpP~rk{i=wiTign9L^Wd-Oce<;U~{BJZ;r#(yDPE? zhjn^5MHdVwoyf9Yw0MEpxNw^xfRwLl-rob7%raQznRoH%H;d&e=QKmFs9ybeKhF%T zDX-4`(HJQ&yDw_Q^6#&$LF)^tE@aV+Z=hl?M8CZ^GhgNyAZ|Y74~9-i@kyal9oAnY z#qVs07L^$o8$uNwlWcn*3#iyHadM}MZ8w9Vsp9f6G`{E%7Mu!*9=m#m>I#Q^qi8@O zU)+1z|hCcE*F*as^BpKz;(v1|NF+XM_`oH zKQ@*o{zWq`l*+d=8SBtS{#+mu4!=H-iNRHQk(q#Wvc~(eQF9keg>Qh5j~vn-7398F zsFLUA`ndzwuQLk$7G(Fp!vJ&qG?lVn|Nhq8O7GEi*a;r%mVC$?Q&Jrs&Mj2u7I5A1 zjT`ztUb2W#ud%ktZI6Def8*N+>@{XQzyJ00I2BTBog=AIva*okVq^=r`QtbtT%Y-- zGg*h1e|S3{6%Z%#rChx48W|xZAt4D(toE(ilF8^m@$z2z@4vZ7VsMD%;P_YU-+@V8 z%5(qD=)P;Hpins6mm6RY2l)48T;uyK@0jkd5A()r4hi(;*u?-wYw5@-XxbOrSmR&b_$BqVMhTlve5naE;aXTU?8}2 z1FUB|;ya;x{NVL&{4rs!}zJn`S> z6nx`80j8P?N)hht&4!RmlwVh+QceQ%<>ry!JE3(&_}5>WuEmuc8TyHeq%$Y?&fTkb zwsM#rtJlf`UMm@nyG%;2S9uK@%B(jX$fjE9b%j(M1loE(+LDE3fyV+UZ_JRB{$a@=#X$@QXKZxZA0VRO&wXSslK zunsZ4=nh8!lOP&Xri$I}*N%KIXa5)`RB)cetx#|mefa8~&c?$+j z$}Fqr%+ZX4i3&P!f?~`qXg?80f#lcU^NiI$!f)2L_2eW5stU>N7$g=K*6ZycGV?AP-(1-TL)g!sU&Xk3l|eBbN6U#+#w&s78(Ya-P*9L` z^pl#m42=Rb_1>@^0g`QLzF1hZx{*YpMr8kffRX+;cT!SVj;eSNvm-~pn#YfuVV|V7 zW85l88D8Zh{4X6BS9kYZXT<}`(UALC`0Ul!*4CjFj+CP|{H^a=f++s9wzi`KXQEkL zo0|#3?h-%rJZ|{G>Y7;1XbgPA9q92sq4QgOr6C9W=bzup2jNTX*ak0Ueig*ewsT zAjmi&R{Xz>fzfeZ)Zn{H#iz_PBeRNJCfq{iI#wp`Lh;2N0tTb@-Ud3?dDem)@fCK@ z)YN`WFiSA(xR3_U!;ndltS+~-xp9g$1uo07TD6CVUZ$2DI8vkMVWJseorV1rIDGJ> zWW>hDqRT>Zx0BtiRd?wSvhh3?1bApDD+y347Xflqpv8msS_KP$Fj%;<%YOdQM1`~d z?*r_faeL`beoS012gFOBUSUGt?f?G@(bCi7K%83wleBa59-HNB3B(GENbYcTuT`#r zzI7*bmDll98!IYq$8os1NR*eC$8eh9fs-BfYpDf%JiyiySsub+iD2VVwf0-D2{TAD zf<5&A`GNMXu6sU!!7y;MKtrPErS){eeERo{O=;ZV8zGvY`#AbvD%jS`87=;i0+<3y^~89yga zX%OguVi{Hyz zKtMufGxZFGg^tY*QQz9z1&*WZPPxHd)!Tc$Mqcr|$|G3~C`aV2WwD_FAPXaB zoS|V%YJR_dB8%^acb0f`R8?KA+yVQpEb*AQj0_K^Y|c+Y(j4{PcQ>Mis_kc^2SZ00 zh_ebJ{k^6wINi7JfRW(0{wri7Nh&xhw#BTUtnQG2c}Y7MWihA~J^_btW%JPfeg#Me z>{U^s|Q_W_e$*p%C!AOi<$kWZBIKM}G#MzeUPJ8{tu z4x-=w^JCKHwGl~mZ<4BakN&ncVi7?hK{{1c^UL0tTWafVkCxZV9J>aAF)?|yU_y(- z+ayeRK~%e@1L;`k%ppN1S zNUm{Q$3yki*&Y3T1&+c$AOHp2^6nz01w6AHR-iGtQf&0&F8JwY??ElN^1rnucNcyp zuNIF%blm7R1J&*7jQ8E8&L4mV@PkzoH1es2lOOpaAIDj{mIXoUr;zHRADYq;^6g!q(niW;o`%O}#y2gE+WVY49?TVr z9oKn2-h2phb7p$ALNr34m#_#XY6#_=rF4gt&;2-WQN2B2Tlj#efh{TPE=`dNa=ibrgYK9 zj0_YO5>Q5|JjKy+KFb^ZB$^!*T1n)W&kS|lg<9Y4&dyTZ7V-S1*DV;rfYp(BJdc&4 zV-GsBMC35je0fkh%yVh=md}R|S7FWxw$Pz~rj$6Wz6E@^7se-cb~!&m7^4&kU}wqy zo?=JX7u-TtUaRraMLj%newp)=Tfe94@FA7FwX5q(`+nX)dWwudW=|Z}zLLXZ?QElt z=Jb%y_aC21FxU7hU`zN@0aMOvJG>sCgdD`KW4dT zf5)JOY5Dc0`|n_yuUqiYPa#qilkn8~_~^*vV3+ba);&tebHrp(Z>nE~UB&K>0M(L2;#(i-S%-vI)ns@l}^U!PO+P}2=i1R z+7#*GwA6VA4kQV1w}-Jwigcm#pIPq`mKX}b$*)!ofAyTVgq~?>kq8Rbu_hVn>wkyG zb-Yn^9iVy$y*OQxje;#P9^Gq1O8(lb4v>8(FMPUj_~wcGW(QC~6e}$?fDPH!CKH{b z-1sQ*7kB^DU-@}TlONH!o)^d)z7nnJE(rx8{IerWEiD~0z&VmdKP$gZ;1{FBj^Qx7BI*WmXrJ0WV4Jy9ZGe4^A2OE zl+9MlDjnRN_Dpu+Dh3lK2J)d)aT%Du^uhn(frwYa;23c()sV?^x7{2q4kj>U)2E@)PRYva$(1z;43ayR?~oDHRLax5zB#qK zurpF;-a{{@Nkgcel0tD`8_<`lb10;@+z1RLDjxD~j)jGiJ#j!-ZPZsQGo+I?H7Mr5Ts;e$0%e$>8sM)u?c8+gUcOa7ZIwLYPK6w?hzfj;Lteh1KjVsw{L%o zpa~ur88ALd?j@Fv-(OudA1c$bZ>BaFuNGP+X~DR;ny0?3S?%E;H+VgT7_XeeM4X&oCwVmaoeGonV;gWl(%t+T@th0o;?QLK7V zxfZZ&SmAX&Idt#%SZ0kIZDv04~+J94OV1* zC046Qg(=$&yWS1((3Fbi3bzxdE1bDFBrAX{R7jAK0~aqPZWOe!3oAg5Knxcx=I?pHgcHbN=$uidNbF>pXln$hjTlDkaVWXa zrLI^Ur~>hQ06jzK&xb?8=v5f!zX(;Xmng9qlcS=hh5)ye7iGqDS5N3QTUj+d6?5zy zEd&%(V+bWe^_Et5@0&~0iT(1RuXczCJ3eK)@MKnOBZU-fVSvwM^Vjrn7E{$J&(;1nMgp z9}^?5RvkfXdUhE8^bLqnkg%H*F>7|FT&+WxLZ_0uBH(yIi>NRkkrw8$7=6iOsg7LW z2Y2rrh(o(`=ME@e(iwrvSoUwFG)xU{ya71h<%wG_bl3C>$%w1ic<)<<{+WxGZJ8Td z8>h~fPsj|u=-iDWQ!>{MZeM0FeFFu3)?fEK$(aso6{|?!S*13g9}ofdWB9_JYIu=UutPx)+5B zTiFVkIMIbO%K!`6S#f*@18;MIOiAjd`_B*r^ndls_Ccu;cHY*}k!7|CEF=SS^PgoX zVTc3)l1=g+DV>}SX0onOoB1|)mp>3XxsP}eN}4mQEgnR0mM1gg=U_9y?&%Q6u8rHXuc5cdTk#!~QxBP89=8ccSOAZL(x4&ho%k=4*pC!BY>c)+vp*CyfzK^75imUt*DroL(mFP zzR?o~OtZ<#Td)CB!+;jRMkTn=jGgiEM+LSS1)m8-O+Wr``#Y2ykanGk@a`WPSuSca zrt@}M{`m&XgH3a+ff&0LJi}3U16sy2%qRylwm+!F-|1+Nh?pwOA({^6ezs{=+z4(yNDbdrRFQ%CCQJUeOOm^uaMDtt^*ynQ;zeJoR-JUWj#~f(l(T2+ z2|_O0a2#hv_1$>|yJ^k`xgyD5KtIlpv z`L6dv^j<0=pqisdBWZ~bLDblh+UU#^h$>E?dX5s_eAou=k)6RabUXvu))QEiPY(WGCe9e#Q`Jv zEv5ueSif!LVmoi1D*j@B6JKm@v7F+KuXds7;DaU;TRxSjFi{z;r@3S^nK%9v;xJL~ z05J<*r!(;H566chMOMh>I-BmVLMCmk5h3zd!zI_Pbro@WH9<@hCfHN(dt3sImRl%Ke z{4cR(f9Ug2`%>H0Nr|~7Mm=__h0`*ZNd-z36ydcm6DrX-5W2&!RQXU+!7l%u-p`@^fcsWsHoUPbKjodCagHlp;N&CljV3SvF zs1~MlDIuR8HGO<2b+We_oRmZkhH8}m{bz4&_V*Iz5FvmMMHY_33yr39;Z&fV3=nM_ zxMmQS%H^Rzhak#V-n?ObN$!YHucS3B57^=~#T7hEY94J*m|p*=Z&+|9sE$w0R68{> zuAWPoBAIhgunvbuPvdhFr6((!=X8j#j4CHz`nr31AlQ)%vX@{!D4n6f{T(`bSWzy^ zafP8*KvqFJ$%|S=c-2}m+`7*3iT0LGF|%b`BQ-CCE5OQl(4KhAmSDluV4`+Wufq5& z4u)aB97);&IXAzSR~htu)K`xo$uuvt;6+|h$f<0%Fs(s+tfEHZ=g-LGav~O}U9**I{kHf1bPz@m<2IJ}fb3qswx>r7nH06Mp;1a|V zWQbigdNsaVzgKa;y?-+~9I>t?DoSlSYeg_-{UlX1w)u}X=e2uj!wo|V`&>Q^{zX?6 z+H+!pzTmzYCn`ymUgprQb&Ezb3U<$cW#{WJF}JXzpLZQVZungvk;^l5!|@TH;`>Ay zsu=PVe?+vsjOS75aZVxE)lDMhD@Ia@2hle;qoG=0)Z?V^6zMXkRzL2}nENUDt0LDQ zN`lHiwXJ#I{t|>Ssekiz>S0e2`QBrCxbChupo-e{z*hX%XFVP4n?6-P+7?nLbG-e zBMl-gR)0bJPV!5>^5jUwx_y%C=|JvKn2V6-59j@##b&FB@`@es#GAeUz5KUu z7)rS4;Z&1QLmjW0t8PmDsBB(1w3?8s(D}h%k2PqDWfn78lly+H(?&B8=O74k>1g{4 z1VJ99*%uVtu!AWbq^R{mL=Hs7(Ne#-xd>!e^9Bl)2RxQI5LpZ*R_Jhnmi&+%6v}9i zdQmG_etjrxRv#>I3RIf;k~(ZrvmoTRRGGem(~cSi?n!2DG~4s#dYNMk}Xr3z`9yLa#Y9`NP>j;h|1 zt>!ih80K?Y{n|4x)=7-(Ns3dXp^_---<;0jr0eG6st?#UAHo z8SQwR1?tOjb*8~_gA7x(o=B(ZcHqg1AL6le$Ioz&*iY~NO?wmOdaWK0753x9G4L?Z z*R7ToXCR*k%;-bg&PDH3+Huwg$cTOtMm(;si_9GP8;IAq}R2D1@3q@d<91qj`SC1!!gdOx1h= z642+*pM4;x7O21(R+CPQiE23EPXBl`9C2}Rz|&K6ez3sg;8HmgAj`ilG9<6JGT1h@a2PnwXBE&YC7wo6qKTY!EA}!DxXy4UpPikd(_SF3AsMU>;kAqh-Vk6_ACa?|QkA})MqVn;5J%h%{Od1A&RfG|ACEqQ zTo7h-EUL9+X+`#l1c!OLIc#xp8vKjSIyu{xv&&t@?7LTD<_Gbx>!d}VM4^&y+$7t2 zR~bdS9#yAMQg5!t*xCn5B^nbvd-5mM^3V|4IqLj4&}kg6Qhs!ZkCC+d`xOEVoAn`J z8ipHSOUTF1zqqt?6YZCRPyQrXy<}^4V6<4@GfBw(1yolIuTmdPl%AJ`oWN$`>+cWQ zuyX3-Oh5;ZNO;JkUVIzg>r8mRxvZxG68P;6jEvHUo$rZeBOjq5n}NYWS{Avxg?rtt zt+)N1ZfPtw0%$HbP~rJ=>m*09@pfaD_3qAyMFxR{>)hp6_IuHY;@5H11@Yq-f7RD{ zkj-yC-iP6dr8@WZcmqr?0pS!kK>wHi4sb%)FooJ!q~))*#2DL}m%7){n0s+~cflYZ zLFvZ7HRaGURSMb5W%xC9TQML{F^y%8% zwBW>@|HOnbcC#%DD#AI^)m&U$h@;ajd=awe9%q7X2S{m; zGxv5pvkRpkqut`C>ZK<*E5_}Xj`5)?GYKMONJ$c&wtPO1y_@Xp1LFj&y%~8`pecH) zCJ}FsVogg)`3}28j$rpAVYNFRkR%N(u$A2bms_`Py+YVN$4LD zvKF-d{p^u%F5wNDB>dAmX<3$rX()~Sot0L@Y2~*+$!s46$(EcUV9MzaRPbjMi=gDoKZjNCSkzque#f}vs!2^$=4~B&Ojy-L5qi0NnrRs=8Q6U+ zegkFb-=P;_`R3^gvN98_y`?{3K`u#gTfFB?M}L3+>&>ZS^H7BfCi7VHR)FFv#D(G5cwYrF_5{0{369z7|ec#3<^%(2|%QMo_)K@`iknvJO3*`DgfkAO}#QqKt0{Es= zHgBu60}}|?w~qe6cfay)4V1DK^gsJDwLDg!c}+UOB(*lE8*L zx{d&ilahsHZe~`NgtBsZZ3{eJpM-xmJ>MMU?nv&T*>HOmdHOT(Wn#m}o+N*M z`->|*#9zM;i{bn1s$uYLI8*oxv?TnVE~pq>spz%2{w=K|g@E~MDb;=b`Bx5q8WQU8 z-(3j|*f7nZyDx|v#d>&`1aI>p3QhtE6I&kyr>H_L0ElNyWx!LwFy4evQl3PuEa zc)Vi2YOFE;lMi^>G3+HG1@R*WD;~~M7mk#r?;2SSnAh2UA090AoEx@XY0P};)h5CO zRcNZz`QWS-#o5^{t6FACdcIGgR>W+c1|k{l)WeIrs{P~Y;?0iW_kjSrx%|=U!S{iI z3*Gv#A-TD^t>H=Fq=^0d?OcbP2rycBKCK_1oU7h=O_#(3!*;h)1h^SXUAyOxm;?l( zNUF|jJb4kGLS#YrdyuJu#kp!Mvh(j=7@v-jmbL}&C+E1H-_F2XvN2*i}gJWZK;4b7tE#ZO4);?ap?qa-M zlkOoYiQ~Gnq~Z)|gftPpQ0po-ovV_|_gRq&x-zI-A3OJ#r`J~ST9w>_;M;G-1G}zA z^f*4HG`rhc51A}wP;_UG1kpv~x4nht<_`S1GhC$f>Xyx8Wmz>f|CkuS$2~>i z+Ms<9*V59O*3W^oQdfk_*%3P-W2eX@keL3ra1gDDAQg0s{eCWxncY`&gLid~D+%@1QzWACS;Cd_CA5{nj;h^&!6*6UZ3nh9~m!H3Wnw zuGPcCciB?!9GB|ivs=TZ>z!F_op47O@p7kX7;3ej4=_*o${OJTf!Ea3bPk1rQFzd0 z$P%@Uu6XnQ?*VE@80si+{QHdG==pX%{3ro70M$DKR zeY!2LjTT^k15cO;4`HvJxAjh^rI&NDkB}nXccP9Q=e~UA3Q|lT9&Q@FOS%)$*y~8I z@x(dzG>o+W>-%Ho@#?mCPOmYQyJ?*w!b@m}Dc zY~#KQHgUpwy?-(-{pqEVL-;_@ny<}_7-)A^z4LIFQC*384Q@G`3ck0c(PuT4EOaVi z5HEPfGIeckyGgaMaMmp*-AY;6VL^y1x@cNvQL?RSs(u6iX~!s`mwCd1pS`};&mh6W zH>Y2ZB=)j=*N=B^14%*A;ZpVWYcNpL**^3>nyy^cxi||FTwfxMz8xD3}<$TV}*m3Z;6Jz#7;Wk zr4bggilEW`Fcg+LIU{3#TKMAP0*pm`0Xa^8=9|0k>L-If;qRkgOEYK?exk)44KrDR zVcv&tQa;sPk!tLVpt-T!rS?cbfX8k67n-z0SVbG@WC8%U~+~8L4Z;tMW*yPfzS@Pd$Zo7UQ+~+3t_{PSVYpQF2S z5zvpXtVMiu4)Z%3TwyAYidF5MaIc zh)31VrBT!`2>9_k^m8aR<<>qRX0`KD!U&Rz2D`FBhd%i0~r$(CS9f09d=5IU95 zP{NPistgVZruz*-S}6Zw7q)Gt6sDF{Q^f+20UVy4aVjOv``e2W|IaE)tEoi;hb1K^ zM+$~d40??ipen<7Jy6OFO;Agxg2;B!C=4|)=EifnW=Yl36E}4<5o}X$cJcS|pb#guO?hVbm*Zai#^zbx>lbTxS=y$( zpsy4mHFVoPsaP01>>I&_Vekj^r|6*H`8nmmg9mKhtH!!buarq{-1HFr{@$;+ynJc= z$1dEzl5@<$7f4A@ODp*Cn_<${Q6wo<_|<@&14%h?lgtG$!+ouhsz1e6qNx2DAcoOf zkY8luu#ZB8{E5rw)DpOA;S2y#LIaRh6w zbwbNLSk@!wtzQJ`MA^;Ls0u#21M=lHxn$$uhYlM}L1w!LZ03WizYf(uS~UcgZ#4S; z%O z);7PkhWqsNfb1)Il>lVf;DfRCDrw;s@S}73q9>=&a2qwIrOwQ+^SWptk zy1Ks3X7cOyD6{i-5ajvLr5q*I_U!C8TJ8vROa$Y*95cS3{YkXgnMC8T)~`NI8O)WL zmzQ|7pLF%drC_~#Q>rLTWHuhKz5Wj*iUy$iQ z0uDE|e=i;SEETKWyQE3l`11!mr{AC&hazk<452>;(t4iY&2T(mg8M^A)7*wV9xU%Y zk=cr^-8I$8);>#1OHk7WetN*30CV#)(}8BA4hQF1G}1HppW6uJ++9K7;e|r;hLd7c zngMn|l%Pa;prfO6uql#?K5BFR&fBylDuONY2ct?t7OjkG@`cMj{gI+s1OnC<#&~|= z;q2f}{MW3)vJF=aG#d%DTgm2G^2mji9ui>d-~rg|=uiFs*Ip0b1GEBf4j2++AQyRu z$$|jNgn(H3cyxW0?StMbZnpIHlKb4!svm(<3H=)e^Ft84SXv3{ zyT!d43$c#Y5|?9R&>({d^L42wunVsG6hw)m3lkh4O@L(WY5)j=Ad z*;8S1f;|-altM5uv|D!jp==-ff{S*tfpYTNiA824(MGj>otNd;2!VsH#KC zgN@1HjEpx1`k!pzsMcS6flo>*P;8Gvo5}RG27ck#TmA;bJ-7o(P^AD?G>|TJ&(Z&p zuL31F9f%t7K@*Gpy185heeK(1^6zyKpIMQq(kZdA59Q=~8Kf(P%SuZ0Pn}p{VcTF+ zqt}~>f`AOWg;u-BwgSfQ-@lJfR8X<8eTJHW&8t4R_BnL@G5^*RCVaHxzb@J{n5wpa zZRDY!*X&pYu*wk>`4p`Z5c}b?XdR(!~@%s zyt>P^%8|N%3cdn+r5~h_^rcR-`?QLI$d3A|fJqcR%ecGi5&x zh3$(I=#+&zbr3p$31EG)Zo>&<38HbaIsZo*^~fWTlOg(*Ia#q+0gk)hW-0oU102|e{tse zwTk%%PZfjSld#ah#p7T#byQzL)RFH|QE9Ordk}?F)6)mg$zxov%qfI}{d-m)|MOW)WRYS%RX zeJ1pKHqSM}1bj*KH&bP+>*UhiB00>1Uj z888}d{P)la-hcQ2VeY~(i>xk)lM4AMeh(@1*IW9W=)B}-{1-#)C~wD4$tEdFm0a>^ z0e+oRncXw3xfNQra6Z5WJwg^b2*-2;EY4eSbrcnS59S%kuz#QfnV|d$7xDIT^r!cg zH2L>~ze|cs*cp3dIoLgtZ#UT!GcULI*7{t0wo7fx0L2&a51`uULWu?p zmlSN?B-W^YyPS*Ov~emTg2Q05dI2RZ0M!hjk8s%Feiu;nDzNY4zVkP5fN%{K!ok1X zc{?mR>IylU`rik8>H`B#7HYsTJ~W~F1*B{;YgH;cqa6~R{=MdrD?M6lPJckxtlNKn z=#6U%=0Ke0>S_jy3$Z*V=l9P6&w9{bM+V&*P@iZ*(ukSATuB!SNrizNQ;SH?(T>F;21BJgjqz8ZJONWtgN zZ+mpJrQ<@0(rR<>I|3pdaa?x~oYx2|UI5#sj(##n)%S@M@ z&}5p7=1y>1UxZc#C)^B7)qR2Lvj7+M#W)`$*;JqD<1O1qd&?HIFSU-vDe8zS7&Q4P zKut8adH$4JjQOkCB9V=*@5emBPd8U(#R!y)3$ebH=%f+sgXshzoU$p(If}!%Dl%<% zAicW%KlF~Jq*do61ZSXe$SHZdtm;=^ivBDz07Z>YS@iH|%2do(maZW7D0rB9772ge z*`fNBY4wrKX^^Advo}{T;ilQj)PBADB`-`ROw+UbhMNdb)c!Xg<_?a-eNcy^?_;$< z3-tnGh{lVL5iRZA*so_kQ6l!pID&)KHXoVk)rwJqdsT0X#52DA*z~BFwy?Ul8|1Qo zusU6(huwK(sJ173*;45aX-H3jls)n6ee46Vu8 zY^|y!#RV{3>VEp~KpI$I1LPx z>`@eEOOK~X$Ro%_t{)pCtB;!OV#{%SCd!o51{828IRw$o`sH<)YFSQI2Lg6rd-R*1 zxRPA-yq55Ie@xGXb1*hw)YS0{xH-3xyfvw ztyNZE`&^nAmbT2aO|`p}WT?q`(xws&*eIX%B@Mwf#PO({&CmZBvZ2!s);_#PdRH`z zT&Se7^1@h)5_U!OLTn>Tq3df8bEf>QfkT$io?1|F>l>61V6`>u1{8}!<2MYS!`N{1A}^Drsa^W5Zc z#I&>9vVAx~rlYe_R5@ARfRhoq>|$HeH|gmE;CbPXrWB{PqVJLfDy> zdBayPvqT8S%H|Zzc{hUBYZFC=FJ~{92#IWFZzTs#|5vD8CxHr;9WOB>f)d+|!&%)$ zEU1?!!%z5wNq-H>*9gh1SrYj{7?u61;&D&B^5xnCx;|Z#Ze(yjSQ(9|yc!vrouxpm zpGEyRxbC|Bk-RDF>gyj78F1b+%v-_BZU4V#s+=k&1x%$jFzkZ7S<)Lsk6T9^3Abjv zlkNLNR)tIjYV2c`DiqCh8*gU78BZ$w_IZ1}HUP3-zYx zFbC%MaCObJoZA@k9N18h2-k~?n ztYme<$;)}bNIp?)iH_tlH043m;f2hFT|bH*+p{+% zC-#;Pv^z@al6NXCCuQaApJQEKEVEW2HvX)YjC5glY_d44PbGvuuYDA{EkxUn^_5BK zE1Vj4FHV@@zeM)rK!=Y#cN|S zcY62Co4&HUP;X1F!u&G-{jrEL!KV_I&!@p&S1;7N8&2^}XX-J*`$Tcg9rDeLshD8W zxy=gYkw#T-2$W>(Mx8MFmPRPlyd`|{ga;CEf1J^5zD(SRvh>cY=bxyvV#%_$4Tt2aW zm(_D~y1=p2@MTg>9Qk=BqyS5Ri<9ieuZk~En%wpo5u?XnXti6~S@ioFTYzuM=DhiY zrZEz9NW$m?RE*;>vppx=m(%7{&lkF9kM$bgL%}g#Vt5~7kUAVUoDxptXarWeHjMZm zzno}r#KYSl-r@R)*{<*nTzUsYnU4aj--G04S&3pH|mD zWD>iN=#QJP57D0WZWh?nf1Zsv#m6u+;teSIqUuq0SAU@S7-q5aORG#Gq-jvv9yKxh z!ES%~*JbrPL`9k!h{Qnh{6L+S#VCvYGc^fEtz#IpiX~YAHR-#K>UbV*>3LdL)h3~fan2+v#)Ju@dEoq^%^r_4q-ZE zMsRWESvh#o+H5@sHQ!dSYo*OB#?zq}D)U2cI%WU(+n%pn{j)I%nMZOEVXl192-VC0 zy6U){lb)9s96yN#^ulWQ%RAh}2gcxj-`eh3<5%=m_Su2uD`np9xP3Jq>j`;3O}Urn z7R)wOsS#vN`qR>2S!aJXyi5@+FA+QywKCd#n$2(_{PC#R@xG zS9_G?&e@b-JlG=MBO|kd4pe^iuHWy`3``h{sFxUHa})0yZ@p7O7o@WBPhy1Jd)3sk zom@5$$(E~BH!12qtCC6lg$0N5s11T_hL${GnK{uV5~WxFr-x;rPGtG#oLCa_OZKGH zerucc*Xo-2P_i}6Ow%Qa3CbGUy8B~}f;pqfbT^``jt#U78h(j16%^ZkuLmSZ3fi-_ zAoVM@eRiH#@yqL z-2{I?#2JH8vw#Er9&Ygi`#F8f^svKFKlPY+cv|q0)4tR}3j}DSzqa;mk$(Jm0^I!O ztO9U0xd{F6!7!*-8xvCq7&ku=Mt#;VAi@42hf#LW47!*?TQG_SAk(w5{DZr$g_7@Q zn2(rGH5|r>w6msKj>vTk~Zw9RKjk$vv`rAIg;O!`I)Fqh#_x$nB~7$XC~%;NqtSZSCtBis2HtxF z%M;P856;C)xy{-c(Kil$k9AHPQuQ8uR8$cVBnts(7`@1*fHM0kg8S8-5Gja_N7~wf zq6fFxa3l2F{XQQyZ{G;C#E-XX|IgHMUt5bTiE4&S2Q8_r_}%LqG@; z0^gVvX#H+=BB7M0SyCV&y0KwtJ~E@A9+Rq{Tw99^7$stFWiZZW{&Upc6zKngApXBI zmlhXhXp-O1+CdG|d3;xPuJsxYxA(OcF9HIo88mu#Z@)UiHYT;ahlpqpn%!CB5jeDd z^5P-IA)A$KNz#)yS{3ebmUW7ZTwDRmgr|K)@@SF2cue}95wlK%l&YSBPEF>@SlD%Y zd+O-)=8NT~e4Y5Dlc>1zbF*<5?joD*n`;A!DQJ|n<0gAG<8%MIBGqSwSICm&Haq7EpPYe&>m;H2Q&+1VlKoS@#U4jD6`B(id42v6vo zWj_H}gbkqW8i%P5%GvEP7IOO%LsEb zLzJ-Gm-*@eTYzs9?8TecG5rgH8V8Wb%nrq*QlMJZU$$#{;dzNt9_jU+W%R_I`c2# z%-3(Rxadv^?ikwF#Dl~1%PQZd643dfti;w41@hqrSK$XaGUS4a$9zY`9e0m#o2?l= zu>Ia0JmBSJSeo`iAObzypNOG_>Fg0PYfndP;S)@~$hW5jdD~jC8*yG`QNMIno@h-7 z8vTq?Mn;S%ca0;O8oy9cmt-mJk4txiW4RK`ggQ3yI~oRe(! z#PV8T)B^*6b{6dh4IOOQpPbKAuWS5xa(7kjRIRWSrG9gc)!yL;K@YAlj%&kN2K9pw zhv8bnFF8YBVINbLey|76R)P!|q4jpNtnxOidB)x&1e;SLW79M7*=Hfqd4L%}5 z!lr^UivbrFJ-q@*^B9GMC|UE&S>pJZT6QOYt}9;8ERx=M6~|rXRBW*c1FA8e0&fmQ zUS1yFd=nIToyqs!5B+-ESD>Z|>1&GoB4>B(W&p?;LiNJv%sHCo2t1Gcq~2Z)pn#F3 zr2@2oK61JcfDO^}WG{_w`vhj70qtzf+&oLH=~=38E4%AfsK{BWY{zF_Q=rcD_(Z@i zlldnTg7aVIEIR9vsaQbDVgR&rF%ISlvw{6pdTyk48OajrdXRCi)TH>p<52W6P<7OgK1@3o1Mu59xO4 zxsG*nZ9MVD3pM2&8yc8$65#ms$n)$Pm=H)A8^eCI-1lF7sd<>keB>hldoylxbU>DJ zubFLg{+#1&sAt(2k=bMOwU}}!13r#(EiA@d=(LA&P*#U8Li#o~I%qKT7e!QvSfe;4Wt;iq!oaE5%Xp*H zBV3W%CZiSoA*->##6;X2lJIk5|N0vPL&ctHsl|p(0b%?OJJ;N+qF3WYPrWpL>QjIf zuhfnPpjiB6R$q?JfkmiZYDr?!GmU@}e9{d<#$G@>PY%|b{IM4~yDZ;Fl`%r-fqnKCV!adAHX|19AXjWI!6gD60h^_;bC{S>(f=GRH~{W>#k5$Lf38o z*Z>9uC2#snna+%NC8n=mC}vNmiUQ5p;CFl_^h5gkUa#Tg?rn1Di!nTP?*Y;i%m8E# zM8;eH3B_Z~qwYmiuBoZvxtelhvij)h(Y~E2Y_5MS4g?!$3ko~qLLHd3;J=5XWiYzS z!p^uRJ=#dkb})q~F<&NlUFXu&TvW5P(z{np`x-I`CALr`k&2m_8Q2sRK+fj*9@S#8 zkaSH5_aD%a%qJX!st<27hk~3#D%kz}cr!>2H5@iA{_vU_?awx_RG<=d9qK&_+f=Wf zf`&QCRS06AEA@#^{nabmk>T*AC3PSwzZk50lc9`gZforR~{ll&{*4dungb zvTK5yp;8iiiN#o%zG5Y@-x9tlJ>wBF{YXgY-G|9mLdqR1F*M-=%Q)Y}sp!MEC}5-Y zW#c>(l=$Voav7Np=L-Y7nhu&RH6pI}{?4D`TMTigXn9{ru{Gj|@XpsxFdZ#saCFQA zboLvdiYvUUfnJYqA;8R$Ur=zMDDAF9C?o60b{Bidr7Z8mhfQN+u?Opdx>nPSEt{+` zFFZaI0W(Ny5vjfh9l#nKZ%J9>hNK)Wu}Z7(2`Rvu-KowQ>CFfY&a#gmBfuQ~hlO^u zaafL0m0xP3-eRJc?M^ol*uOr5!XV-4-o|8T)_sX5n5U`^+0XMyhtO?br0doivg1ub zq3k^1_ft*T2o>4P=cOU6?zMtz2JS0f_sQ$(GxPjHHcJrX0N?jzpVA0nd!i(DR(RzV z&+L>|Yy9Z;U0NcIPe^#6TyZPyS@;u4&Crb9i)2-Y@d5>!Tcq75@>AyHvtkfA0lS;i zYRVlt={uk+1f;Un^~WJYHvN-@4{i0S&%$DLs9r$q{qKrioNmWwHI*zK(aN2x7EFZw zEVJseun2p!b{8`-aXkBZihi*Lufq zBSTncXLAMf%ai@Lyyn(zlapak;Pv861Y>B|DuJbCGeR<=rh#Moy#FfeUU-i zNMS<)R1q1EwO+i4xQ=%gv2F)i&KuKS^-x(uatuLUKwMIikcvmRo<9?Wj*g7o>Y-sYP9))UD|O7WiQ`jRLeL+kbKw z<}tUPV)_95i1~>V+LI!IXHevPfC(M7QQvDJ*!+_F=Myx6*e-ok#5xtX-Ad*IjcD{_ zXvX}L9Z@Xfk%U7f0P&f{$Zcltwy_5w_Wtd=mrP~;cn7fWQnSB?^`*7#EG7}QBXQh3 zJ4|QFZen(t127119<>7*3$jPH^e#L1Ljqv}_hu=ffstgLpqeiRj*Ydo+nF7@MqW`oxVsb-OjZ*l+U=ZuA8hcme z?l8aW9_EJ+2``CFV(D4Qn19Kumh|M|7EH4YBR0@mc?QKlq)i~g zNhI=h0SNCO99Y!4DHQ@q0P0q18oBWF7uCgCf(ST?9vE0n2UJuzLnaPfZ~-1Kw3(&8 zG^QZA+4b@Ak@50>-J>NV<+;cNjctR9Dxqz5-HsSseGfJ!72!HyLgvN}O$A_{WdTH= zK!??d+BkE_t-y0g6ZtT{P$DG`qu@(-{DqaPEx@iD$Wj6wS`i32)K8&hDo>Ux)nFmk#>*AWw!y+O9?-zK%l0JBrUcS6x9b^U z=T@{mI(@}W*2rM~KN&@(gLtV=)yAwza}d3^mE^4oy1IyW;@A3~JR^i>kR>pA-W+*fgNDx5s=b|key?%B!UWGi9o_D68Uel8(Y;e--352zi7xNy!kciskHXfi~#`WGl-7%c9 z47>1sx4!RmC4p3RW|c22%ulvZC zTAqf6o|uOg8~}lw_wrBMUG!T}R)^hV5{Lq#!9anIS7djZrCPyjq^Vszzs);{eRoz= zNHt0>K;tl_N{kT7jMtQbG(*c31N;JD8_-&9P{#9D&<&oyrz22LahV)oZhVNt7ag;xnVThll>ISC01gW|Vb znf$?;F<^Til+vDMy12tAe31l$GMIKylAoKZsn+^w@Bc=n^CNSn#`hD+XEpO{$2mx8 zIOPnPj0zoa7_=B0!6zkcN5(gQYPa#kFFgR2oBd-#fPfCVZ__$E2|HIW;jK#uL~>Z)92|G;*k_@&bk+>oa? z5^E60u>Tu`_xrbkJ=8-X7tZ*;8IipB$8>}O2;!jD6qzalv=PwgN4`Z4A0->iH&TAUFa8uoAJffF}RNyc|8!A#LdUb9%RZD%}BX`D8vprKJ5 zFFQc$wLPhGGL#Y>{Ng#ivd{!ynv$0G>zlkL@Uh}${z<WadrkCm&9h}-StEiV8=3w9DMjnqVXD^GRGDz6qY;154*GPadOT9~3){|a;hw!2 zgMdIRP}Bx~m=S_n>T>Tl5y?Zgj7BS7rnnOMoGJwDgx}3^VUz5<>H;vdPU`Y2P@j)n zsjV6tU78*ahF}gDXkAbp+B!VUG)p75Urj&54!rkED{S%i-P_Es{8%;14UD6zWa9=x zk$O;&F@q5V5P*_`36LTxOsFlp5JfD?EELlx_-Txx8*F6Iod zh-H$*20@Rb;~n~L_N!6?@Co=E&B9@0&aObI^(145p`YbLKLa)jNO#8@+rhFt;=CGA z#$MRR5yS-(Q7g5vqn;@>HFK}?b-d2ehg;G>*i-q9yVrZ2W~fj9ZP)WzbYuq&g-C(g z@4}vEF(_y!25&W^9@!c1vWa|v1CrliBr+ejQ|HDR^)K^~(ERzixqf#`c&*vHIaO$I z>}>A>LkQ^v1op}c&CASxvR?7Fco^uQfz{5KDnAO^cZ;5ognD@ z^-&Ki^Z@NWTD<`vR*o?b*`M+RWrNrnYcvX8nmcX>YtAm~^j1^vl};vsVmg?ky?t;H zpt$%=z{&inTea&O`5z(!f0T2rTiRriD||WOYUxRTdGI0tviRBc^I zuPc44+dSpuZ+_*f4OgB=_H!buuORbMW|63_q4B`MU1e)$?89m&5j0a%c!Nv8{6ys= zhqqB`PNvfBNVu|{|LQ5^4F9(G0d?-7LE@_{u^?A z3k!K<1FW59cy%COf*|k7n{l&};cXWF+ow?u%0I}XlwtPi@A)x+%R`A#SDAgBxxb%Z zp?-^PnfW%Du3?m{wcezdk;=pkgZ?KqC+F!s7I7t95w-mH9(q4O@|eW*e+h%PI(MN7 z0+^wG0T}UHuLDwirbsM)A8EIxj61V*%kvkT1ox|!<5E46EbtlV8yadIuoDE_K)JJ` z(|&bG9t1L4p_3YMU;PfBaKfv;soL5qF=pz+a6f0Hg@A5^FQYu9XXd-9i{`h z8QH1}c#Kk>t=UjPjCW5W0~wq(&cu`dgz*b~7nn<#N?s?S$#|7~ILjaj;U?7`FxT>tVaYB4@_Wa8t zdm{qsAs)ng$SCm3e-j$nhLo=@P&j4ra5vhSqe4eAAY2>a4TvD=(xtn9HIPM?{}_N2 z$2N;b@s|Lcy4UGqgSW>q%I85m1Hd?W%OBsuYza+Yog}ef%kv7s+p>700>? zKE94*^$|XD?hQPa<=f??^?@&Z04Fp^?Vl_c(THF*rkpwh{rD@V0WsuH&t`5pi&mV#aChaQQ>)y5tU(_^|A;4LYibSrSzY2*$A3$jm zxN)&Y;0{+U;TXG00}1rZ^b=Jifg8$KQI(N z{JO7M71a3;2iK*eh1sdXbC(wHEa@{q;kXC;Dgujj4lA>b*(JlkZxvu5*NeX4JYhA;9cljHe?vg&TeHqKB2?v$=WR*YIU=fm%VuB?K zC)GH2eTw$o$b=qO^1XN3rwWB`m%+`f9+zcCr+TiB%8k3N??rZBT9L3#(}GdnXGp2U zF=&z18$(-IyY%3cCWeM)tVne0fHb!m>brlA?TKD}})0^n&Y~Fu^-IOMu&iqOs8mqTyKTD(Gw*Jl&i&%I-0uc9A5i=?IcB6f@W>R;ig!?%|@X3>b`wzQXFGvYg=eDj{&FtHMN58 z*y0uZg1Sr+lxcEtIT%^4I3Z%8Vj;zlu9edwK*c3=RI9f5Po zyX4AvY_)q@EHO{SU)pob($O(}`y3ImJ)kIxiuk5TVeqH+V#*D*lv11 zzQTQ@l;(fyfqgk@ig59puV24@Ms-&}fLu13t>MJs?IjhjdZ{h?&;4gkdn-fzSsrgc ze?7*0%eUSJhDg@CLsWpdLL8q$DsqPNGC$W1|0Xu}{`#Mx)-e_v5vR4kF7WeIWBCCV zb$M~|e{FuDL0K^;vw_R)zkjz@6Jj)VHMPu~i)(#otZ;&g6N?515!@5PTSHVXARz%K zDsL+91Y>Khu|o0#tww9RT>M%zn3-Gi*057eaXZWheMnT1 zizC90b6`)9R6CxBwRm0ljEvhL5R%t5-;+S)UsAAW0F?ESVxxy5r)zd#vrYn4-1-vR zjoDc}z%=gTQjP?|mUvlR<0V0}w?2v?cHL2&>@Bme!63chJ(9P48%;E_<##rIdmxZK zMq5LgRw=+TXMEHOT?7aOYur~W%fSmV6BGo*P@LnOK|$?%6SFB_?K; zpSMiyNcBEn#QLEG-oQh!&CvyEQ~l{$G~-9eJ>a@qMNm-4SF74%a>Ul;pCXLSy9^7o zQ#4n)IW+9rz3UJ`dL45 zaS4K}J;?BP{PZmvn(+LDSaKC>{eDT&c|BJ~#yVllLEpVqpW;$h-;0{@SRRy)9+>`wATb`=<<6&v{8kK~z2&Tx!r~U9_+qPejaBPO-@H_6HB9a|V zIa+r@aE#*39K7 z2S_$76)@K48(UV7=Hk%rt9-xx@8T?_!+<*j%F((Tjr$e$4CIL3A~C`DREK3xGL~_` zu)`OsQH`yw`^%n1bO8n#xO1UxXq~hX+B67$9^8pcNgY_{UYe&a=zdjF~ zsh+=z#* z>N_!^<-WgsZDcHpbG+z)njZ~;-fti&F8=Lm`yq9Hjd3w>7)YUoaZ#Wmi0{En4TeC# zl*fAZ{*=7#d=I6{-dumqbUzw4I_BZ^*3D49yAK}Wl=1Q+87puBP(m6;{lGwohOr0D zOwoPSZn8i&_?_D}UUbUO!phnQO`avxfk8ptzQ}cqUvAQ42`Vplvt-csmixw%J<`*^ z%?Bo3zJshEVpJc{`L zbp-$I<}*<~{qf&v2IDH$PJ=2a{dp)3QemiKIP~V~E~&}ML+UQ4!azo5 z4hF(zWsTvCcOLE9Nt{*wpKlg{h3@;){7xbSh~9K*1JE$1239QJIHv=T?cX@$*7^Nq zeo$m+ZGb%4?jgUwP6Y-63PYbp!zJLLp$$sC-$`{1AAflhvN_X<3Vc{2g>Jj%FLCc% z^?C~I#rw6da;n$3=CuZ#J576p0@99x7%Tf8PJ1qx>=N-jZO)7BpChVj!L)BF#UU*N z4;7*}Pm@nBW{dZ`YTldA2BdE%DvE%sDfs=GYW)hsrB9l#g%4%9SdY^!k|aN%RS>mr zBe%(sCJFJE;;dRnf^Y}DnUZgPBl03w9q;bA-URXM%u!ju8GNYKoub#q~+%)by)f1Us_r^kwQY=(IIWxpUzoaTx`8k zpS1Po;Vg>x-g_4AO%B{_GdyT9lRi2+TKS><@(&J+P8sRykk#X7&xjHAgDv^{Qc-`iuM&kzzHIhKkSa9hqeN56hO&7^0ln5eP$-+K8yeF)#P zRMNPj*w3&W9se<3UVLBDbn30@x&}cT$wQM1E!{Q-I=TUUJQ94Opxj&*a6ltwR%2@` zeH(xij)j9GF5cYkRTG6{+HWNIL<9Y9=c|rYn2MHL<-0;yulRp$#&d0lsXFE z4zv0F`TnksSdO*V5F4@4d=C}T8O@h__&F#D>-vE;{wIwDQ7>wwx;()5A0r~r5T?%y zFx)$7#a#ag|NfWP~>UjIeol9&IVENPhxZgY>2Hh z4PqSn&6pKcRUK!z*ssmaS%B7Z)6Gqga(cJ=Nib*&^ey!}ae-}sH3-qGzalSl91fkE zvz%}6rHAJ0Am}r>!@z(J%w6cZ@vW&r13_u1Okb0fluQdZvb-x<8w@I7;IR8fUFfn4 z^})yO?<%W@GZEQ#90sj&5d0R@hbzJ!+Ca@l#}x{RDbVg)D(t0&-Mfd|(9T^Co?T8^ z%x?TkchG#x+~uY7BIg-G;D;F|BB(Ads5CBIce1tSXn%MV`Hz&_^cJInyJi7(Z0g48 z=AhBcy)YV6>w+$l%VR3U*8cEx4E-o7LTB1`YoA94z3Cm7^VS_ucDRPKGF1QhSf{b` z`wteI=E+Jtgn!xfk3m89O~|{-CcLLGQ_>^y1WLz zu))sEKj;NB=W1XFqhc8%*`XwsztDoX_IO7a+TL{MTXB$87%*k9*fAht1Q}R(`U}B; z9)r_O_AVcj$m`3OCx@G%X_Dc|(oL_5?`a~o?mWOj%&eJ@PzpnMPZin}%c#zw=^3Fw*aAWiUHH!cHVwkP`7& zxmaM<*0x{dS~SdZJ_ryDzd$E8l5Eo6-d?YkRO=t<<>@O!-R*KbRT;Vw%x2Y`R%r^`cB zrepn%Gj=_*2R-q5Y7Cj_4Zrf7@jfxoG2K8u`;+355>Cf8Qn{oHVIg6yLZe@b&IZ;b z8U}C13ds*F68urlt7t_oa6z;2J&@|V$Rn~T`K=L9<<5f6b%8S8T$7a^qw%eoCs?ir zYr*yPU)Ea0{|^`7aYUQ?ZEaA`qVYNdjWZd82KJ=n_YMtecmL3DKVj0+;*b9rl$B-B zC?SqP1{#l$M*2X?q&xg=G)qPJ@y164f|qwFM#O=h`1dgu#1An7``GU(j~TfhK3u^4 zb$=k!>pJ2{xBt0sSx>jS-y`fnbgpl5$Mxw&L`Iu%J|cAg9d4%R`!~M8;XochGyha~ zqJ;0zFc=T%#tp%lT?A(INdcbt1~hcrc(o`t(i{OQqkSb zAX#9250#=1?d?*3a+Rolk*o5U($t;TQh)ubK`LOcUUYxb5({AB7T^!`7He48zZsU= zuzvmd=nfHLW@Cfa=|0SHdKl^SXyizrpEzWxmJ)a-oqw8`HC(S3f&78F_? zmai>nF!)`?HZY@nG4e*;(Gja}nWBPAP)k>NLw`TPX3b$@a2j)!=gD>*!W(azCwI6v zo|--baTNoiBd-BT#c#cny-lT|E~>7}7**2{Y^mdou5u2yR<-9$kw;5ucpM%lhqi&~ zB~*fCccF7#K92k8q^_v%0jI$M*57|3t?i%EW>z|B-yjMh_|mF$B?7=mVz86wFYY2w zk6VS15WGTsA_usplnJnMFc2(93kLj;#yY4zysmPz?{cRGwxY-X6VTEx{QEaItYgwNI-2tQ zw^N^_iy{MWfzErG$TP4Hw>r8w-TaFyou*Opr$!UpIUbsxQuNu3TpV%&smS1OyAtvd zz_;WUB3U~J!!|HgNzNP*dovkt(wqIx?2m@iwj_ z>`lj?Z#?I&A|k|#<1(6ATDt$gQ;Xhg@|A;8U9GTY7gb2dh~_u_!Qx|}>a?=gbu-xy zVtbuC@FH|~nlCUcyo6VH%Pu``=I2|Ia;2Ld%EZ2McIHRG>2;@4tndD;Cl@LjcH5XE zOj=6hgi?X@AD8A)K<^Bb{~JB^7lo=5ZF3SO0K@x+i=2Q^3FSvYdc<-{5EtD)XP)q^*PSBI(#WAdzT|SyCxj+lNt??^}NXRcO*SsYBVRj zr@Wy&6*W76B_bzMV*Ddim{i$xFilKMAVDSv;O&bbJqyHv2;Ih|DGUo5k5$~VO#zI1 z_wIo^dvkLikp)Ec?VYZE-y^X%C$e&e|J;a9(wSvegJbsuxyig@)hR+n^rp$$Rf=)O z?QDb>9rd`hLniQ%))$|~OR`KI(C$E35lsV4INDoGXZ5~We)JP zO;1m6CuoBb5egzr(w@)s^r$*`q-^&-FuAt>#od*s)b1l8OxqCt{xT@AEit?T7Xg}( z)={}2MsbduL}42fN{4$OcBZ}Yyj$^QidrT)DbJ2P*qQ2%6w<43X>dew=`eRR=!@4smK1{1_>g|wrD0gd}ebq z9PB7(UvhW;`B!6{=oqDgZCD$a;2Dy=;xWgytbDD-KlWo}r7iGyrR`XXuqlw?D zS>i;>&wneWno@wu@aAbYbbfax$)=@alD@imr;X-@mP%_jT+Lf=UOF~Rj1Hh6tZI0$ z08`)_FM{Ln24bndC>VzFcvuxtztlG`N3Le`qSoC7@z{JqxKhmJ7odm;M2+iVgy3yJ zPHB;gRS9+)nMmbMs({>AJo(-QwP&QzHQNZ29JW&kl+rUYWp+>{#Y4r4@p4CmTpzXX&= z@nP+LenH(y)mdkJ@%^>#S%hNUm#Zh>L_WbMC?zNl^e^D1JwMv3nOYjpBq1OqB!4X3q0yafg=@ermzG+Tf(?FCZq)FgJ=j`D&c!h_YvL|f`>(x z1D7xbx`I=+Y!3J^f5` z@O%~&Ox=a{*}rTp7F!^^`KBq&>{%Ej=37_%R$Y3MCm21r2@xFoIqx*zc|$CRi#)jQ zLwx{Yz5nVxtR(Wf3n$dEa*F^!4^tGAUQ5Zi>i)UlwNgE_++Xf{wrP=OIn~fdIJ>p8 zqfcfd3@BVG$R`E;qz;ACzKgu;@L|-QHFtVw6h!vScaUQv(K+NrvfwEZ-l1x8I`>oW zYjUi5Kzk|{lscBiN|}Lghch({&g&@PKlW$HYkbfP4cUfow6!h!o$R4;<2O07Gj6v> z)bw91owGy^s02dMy)2(bGDUKFoOl@p`vCHSL{QCM)>H}1R@pz2*?5kXFnXEb$9ebk zDjKw3_5)ds)tYc9+0n9X@M7$525=2!>`!^lmx&>~K?h^0#zJ~=c{w;-^!T=h#%RpJ zISsn!8Eknmdku<^LAA`a{L@j73kv$%1ei8ADDzORty|cfBz4&aD&P0ua^d*qC42*n zFvXKZYWm8ptbpY7baDOi$bc61|Ezph8n>n>Un#{N@y#iZnVFg7!-H!#X)Z9Jh*0P; z%<0bMfNN_@P10^tAUXLgi8krZsuC_Si#n#2)h6mAHhlaJB(!8U_`~dBx{2ZJRuKm* z@-G;Bmi|rE#K|h5t^6Pvf^l;x5I8Hdb1j&8<5WVW`TW;)*Ab8) z>-rkp(*@;X)81qh)x2q6HjnA1ypd{S;VD2H`tmMsr)QYVhKmxXNE4njzaYR@>ZAJ~ zaE0hK)WKUp2Zb|PQBy97S82lAes$#)_)l3?oIgM~*A?1k@z&rxUFqPwVMK3UH! z5y3opkgpZ|=ZM_op5)p7X>=C#OXZlB?1#SlRYPO zbI0#&k}J1Nf_zlbQaF0WtrRwLU}9hpb-FbSkE1Pk)Xc2Tqv+5$847nkk$EG~-_Kc5|reNF-h5zO!nTwnlrKq6p~zYE9}YoBfq? zqBeMUfZzIv1(6I?epnc5pcRTO?7G(|dKupW6kceXb=3Mv85SCyF`R*oi77r_X0bh_ znKnD8Kk-od?ru{{>@0V0~?#c(dqn1=bez-h-BIOn!ztqSxRr+jEbb2l`$@tE_ zMNTA&LO6Yi-|+x%0XJs^<~jngwYz(D*wgi+!Mo!63P%=9js$BPo7MyEw%+0a{#>nU z&5gnkf5=l1-rx@R-+K^7-gm@zr8GudO8)l^zJYgy?_wt@#DymMiirRDC1@L{nl7wZ z!5;{ER9U8a@O~K=|NiyctV{ZkAb9!AIxiNp&Kks$73xouF5bbpx`10CRfV6SQ1R~F z_YNv7Aiym@xh0|WMG%vYX$qMv(|E;yh~qOrAcU*S>eIK;X4HPmZuEw{ANv&`mU7?U zZn1Fz86F1h)x!~h)jd|4vppX!Is>N%!kd?MB~>z-*mV3mH4Ce37uD;O$O8_-hA@mY zC|9VLzA2rUy?SN*=^ulz2gVpV6_fpkjOJcq>r7n(c_lRIhUp4S0V-;1E~mQ{biU z7u2O>8>nF#FYH8wc%GwL_}@?^BCl&0{q<`AmFBSCZj=STCl^=obAE_Xvn!LH+q16pa6!l5ZAo zVD24CWMGMGv901N;+Z&CRo6F7k{Q*6=sZ=NQ2dD$0pS->a z0wy^2G#u#XC;M%RqQ5>0QX-dP1ae!#H`9OnHWa+cu3fJaae5#uOa*j9h+@2<<^{nR z9V_el=w-$o9v<`}n?viZ_Dc6dix;g}rEjjKLeh?zPZs1_n$yrSnu3^$^!y#5jRMYZ zI{z)VcEs8&Pz^SrM2dtxDk=n#|ACt&9o?J|0l`MiUe)7ETX3Tym8U>YlNcB)ZaGc8 zuyPL=Fkz<^Izt8c@h>h^iGuc;VgAVE{{3s{=bz97FqMsn&`o&9QquD12*4y#yf({7 zMY10L6w)>d-h>K|>5%8mSLK$zEuqov1ZeP^WPvameb^fl!A-MGfklQL`p}qwgXm3> z7yHR)bBF2r7xrFtM^lTVz9bHHWvvg#+iNIC;E)0~R)MciUCD7s?I| zft$GgIiUtVly$E|@!PvQ8$fbR&&*8YP8&V^)xcb18~hcR${^r*8$e~{76HLKD3qjV zoN^vnGzb6$!@F8^`KpvK7*Dx3O%jh$6LvjdCPJOh9?(Tbci@Xi$;tU;1~Q9WT3@TO zH`U*n7fPx&GBF7R8IXqY@$tWHcVMEwYk-#{g*Q4n8Y6s4(*1Rp0&$1C0;~4Sl>XgY zj9A0V!;O0e%@tPG`(vz-U0;VBNcZzOILv*zf_TVgpgrSgZ$Qx+MDP%gqd&-Rjefy@ zrot`ca6r&ncZjB}Jg~hG{ett&A5F&hKs68gi(Jj^m=t`L&Cm|^=_EJ)EQZf*Zk_X7 z_pRo$f^(LWx6HN2diYQch2|tw?zck|a`=S!_)1TZli4u6yZdH$93LJw$;F5Gc*>FS z(V4IY?OWtpWJG5_m8OPwGlr)X<({mqU-OgHYOCD+gegc943!4ZfI^3N=~G@FZoY}! zRpgKd)Tdzjd-n{$DptM37}=ok`b6`2B-Sm78DqIKSt48ubMxNPw|w?%BR4>t>kI80 zPftxEzA~#ZLHy1`S@G7pGuwofX;**TpKJf1{x?P;^WFM!sw|xs zJ|CIhP3RF(yt^NgI`QPTk;Z8KkQfssf8NvI29;^FSJ!(@#+)pnjb2syyuS6-hG_^d zqZWp1?DaL_?8YcxCPSkxDx9l3Tpm?N6Mpre_nQwYu}o#!gV-2rvO9t#F{MIyYN;+O z$Q#W%!p0%sC@UZsin5hd-s70E&Hlq z?SK4)-mTSXc=e<*iPd=GXFuiq-?LQSeef#Adig#(UQ8tm-r?u3=E$2+>ab^*Zq8kB zY43^<{G950Bl?3Q^$Xe+-UwFYvoMY3w}p(j$T2vPN?;JOQ5hO$%hti%f=}W4N37+q zwvtNRa90f{HB5j1c2c1uf1)(|F#+3rqz(6mHplGet^3#cZ9kA){ZU`&2fysC`;r^? zu|yfQ2=b?HZ-0qE4Msl4>z%#qsyw+d&e_kmY!dz(LnY4<)u>iK*{9~Hu!q4X^{gTYe_7D~+m*-i7Z)=7pezo|`}dL^c~C4BP!okvHW)(r&%&{?mu)4ViA#{d3a5HB zg0dil&Od_s;Q%#$4aLL_B|Hf=8&4wL2$8)^r%a8?=_DcK!sOHp#+^BqthblOHwJw~ zQ3d!^X37U$Dy-$dJm8{ZT7hlDOivRdOHclPW2wTWy*l$#e!@q-`y zREjV=KE;X=P%tY#XZRK*C40S}%ln5M{sXi6mizeSgwE^A+r!D4)4{OidXv~$X)c4vS+~WGNNv1OoE&ohPODPHQ<6_hRCqj;&aj2&X*@ec@ zSdYRNK7_Q}cSaNGs=PXJxTr<#R7VN7#iC=PV^VAmMVQI?8E zy`t>y$B3NEgGhJumj?qAt5FGPltMXNY*K&=y-97iN?)pD(;}3e*h=Naxt;ZoeP%e4 zsd2Oy-N|H9k_%oz1yRr2q@X>(A21&;8e`pDdd`tU|MGxvYZjB?qFVhjojEz) z+lPv$&4pc~xuK-BO{%_LVXPqV?+>Bod&KJe{sGZB-kVcO`rE`rxbeoeiW1W`hLeO@ zr>;Mvf|efTH%TzO_Kkh&!=TxN{)LdksDdCvmS{;YSk74734M(BFRNcjt)Rfc(3Hm= zTYg%6GCwC-ramTT>==%E`^c$1rBTKDnA#I6O^%-d*O#UL`kG5ORWZ%1c7%QoDU*Cu zAK)?O_ErL4y~tO%O5BhX_dz;AULCct=4fU8zB)eNymv#>y6 zQ$vn=Ql?TrpyGOoCU}4A*Cq>(+_!Se0-1uASF#!iPb2h~*>{p1Jp2{V4Lhr_mqulL zc&d;0l0w*&t{o)4oPFts1(N`7M#u$~{#!_eji#XyJO~yc8&i>MY1GD~j1&Qe3m&BG2^&7z4YScdIc5~Oohwd zAvIbr{~8&h%zm1Ci`N$v^uFPd)=Rq4(Z|J3t53cMS&^(HnF}BOeXzA#ETr|}bFIyA zL5B;+%Ogr`lI?AlQ%_zixu_`54Y~hR=bxtNFR38I4Uh=fg`4bM~Gy>)`RK zW?P3S-Y)Vi*yEYV<+Zwp{O#F~PnkXmX_sCKKfvMl=;IcX*^z;I<--7PtQ_eEZj&##=EZI-`{mJ>RWchTZap}nHyyGPQ`xysD@>FIPTy zbY!n2Hl%ak;cPkDy@&ajfo8p9>6bJ9caj|xZ0zKV5`B7VHdJ`Y)`jTQ#;D6*>9}aJ zNdiUOq^Xk1-7X{)(wUyRR$*-&4xSyJK3GX*YH9eL#>9PtXl6O}INk5%@uiDrWC_}g zd4bFLkDL2EyQ#$+s4HqDGwZbV0}|)c;YkR*hsZ6iGTi5@7a8wiU)jx>fAsZD5;aSC znK_6q<;TjgL0xa zr;Qx!NA%ya%2}6=B`y~J)ID_MT}gdnlsmY|zK{~+CcUx?B~>Yf7hcovRc;N3f192l zk(kEkQDx_$eq12%19qNl9k*pt-e*7Tg8W!cZ|l=SQKM!)2@OsQV6EDI1{stL5&5p1SDY( zOZ>>nIp@h&K>e(o#DGGL`t8GDomNeRFo#3@zxB_`51!;p}dzj@$ZTM?tPP_OOmuPng*4 zrD26#v_RJDDwhYTDm(bS8I`CG-)lT`m5IH>O1+CM10vT8G_{d?9INzM*JWZ1-CX-4 z{Cn&{&x9Y7j!F2uxrptV^decT;SMaRs`EYwBf`v&pXM#N91q^5!%bA7-HAHRFUR0P z|5K21k^l2mi=w7E9A<0$sg2o~O$N+Ct8K%w^(1MfmqS0dk6-*XbEY_HHq}c}*+%}+_TMC)!a09b%*-Q%EDDy|}E&PT$`Ra{Z;m)NOO`n+0mK45Vc&+=S z$^jvVS>eB3=lyR%n-{vD?lZZ>SStm;9Fliy;^b-iffHTf)?ZLmit$-_@X||BwtsYq=8#&;Tq?U19OVmK(*x7F+Qcp449rNUjgW-grG zSY%CPYr8)ojiMzgP$Ou;-p~1rT@8iieeY#C6Svf3(~ktwX7#y7E2 z%fln%n8)Qp3?fn)sHBsUKHpNR?j2%Lv34rK)yS_g6YolI#`r1+nT$(B7@XC&&LUaS*bMD=3d$p zc@!1qS2J~?qjnPMH%uPA2>z0oeQvDNCpljDbuLdH4V@A1p?2|p>dFPG2vvq(LfG=b z@u;R;8P-$%kiF-OVZvGjRoue`MY*8@gV)@J8@M}$>{O%b$I&94O9D|dWWQS0)h5D1 zeyE*0$aLe#{YQVYf9|&V#)yg4SGG~tPex|CbB`Hbz4IAu>pVbSd49v`9D1(2Yn)N(>#+Fq8^I3@M->BVE#+ z0}Rs5J^t>WaPNJ%5Abwm_g?$EK5MUYGC$uD%e)Z-*jdNqgBL838tr}GNz-4>;|5vx z&$tEEmg15)!n1!iJw22yj>VLIXeI@Fn}gq&UGsE)az=iw$Ghh>62u8pdkND^~N!ilIx!d zqxv$ma_H#}lJwIdpv{oK`%rr)qD=TLaB7as$?`5F)z>&YhP<7Kq_&&vt$Q6sSg6%{ zbScvNOE0acjgFN`QtP(aF0Y=@r|H1mRx4jcL&*WvQpK(ywixGfaqPrejHA^cCp%Ah z%oW93(5&6CjQ>oS)0;6W3RQI*Z$cM(66bm85~)j}SrZYo@N3M&^ZbfK=#bmF2HE|AqgQJMH5U`kPFqh?Cw6Ld)?7?da_N@?z#+>u! z8lmo?!QvKCsp6hpy#K4#l2iKQGF+bN=?G#p3ec5Fm*|1mCqAaQ`w2y z#B+Qq^ zY!_kS*QI)Z2)Q=(Kj<6`ZLSS5g5N!IC>*lCd3Sq4NJnO1zZV)%k`)hDdXOaRB*wC= z`(eAdalev0tTZbmJ`OC(>e}zf0qFR?RgCH*=rvW{$`VsOy?*P7v4d*`m#fsbG)hv@ ztN53s)L*bEN0{w#a&m3ChSQu35~tbH&TvO=?vI*%xch&}hrZ>$9TFD@gzSHe+Z%rk z#87?yD`@%{?4({gL7(u*uGsAN*&6_zVAeI-2;XxFEfop8{W&6Dwf6mijx0+qh5K>~ zVc-n=P+Q^&Ewmqm!%=r-y5N7fi$_5uR3^Z+N9Rc^{P?G`QN&QOJG`V^rltpj#^u4+q1Hcw6y3G17?}g}4FsaL}=D)TKU)}Exf#SZZ zAiUp^TER}^rpOW67e~5H-tuPyU z(-+SSshlrv5;e1_M+vP?&iI!3=yo)}PDxrjW9{_J?F*3h+2kC|B)hf zjY*9+9Aw0l^I3>=_~v?e zqjI)|dAXF;SCq3Z`|#nLTH|mv{33Li5FzzqmG$>iqtKDKbxwRFn{f@wSD~Acws!S*KL`C)B5_d??y0d8&K5>WPO?ZK#xs&T{H>fj1wAp5*ubK0o+N&Vp8O6{~!O zB8APHA>G0}R#Y*wPv~bXXk4S-)D3&Yw2s`egNMqYYoVi%ILyq}0dF)D&tZ+YY|qw$ z&uDR^*Uhx}2h-u;1)A!ZKVFKLo+dS@&z^R9)mzbc)o~03K`$yWM;#p*8UIp>YcQ8z{n4 z4-oMe{=klMC#h!M(2W|)_p=KsdYl|NSnf6(b@q~8Gv9(!27`TV_o;Ic`wJE$RPm|Y zcIo4WK2|&5j3j)*EcV$`+%$;Y#x)S;6j65~|Icl5qi~bTd+MOlkcs^$=T&{uE;jvWfl%n^ zf?bhqfHo*zL%|NXpM*>~r~yA$d?xDXUBM#7=3L+sBMj3@d681E9L5XD|A#n|;UrgW zX5p4gO!GVWGdh8$O=3j<)@Fr}+>4p&SF^313&K2br;kfoFXd0%l#U1BR`V{M?c-A( zz}H|IGIcpLhaAoN5$$mrcEt2aRN8X+NZvGfSTPNADTxOFUnH?rw6Ue%5_of{0HQZSw0&R-G!W|bNxjaX?1vG zpSv%{5@Jp>+o+%xEaTnJomXo#3rHXIsUSj)QptJXB$v9443@LDZj({-U+)Pbfp4le zIb|wr1ELxV%}4UYw^pV8$U_3mzE5qXW~E1CECyw8IvJo2*uf6>Lb75S)AyG@U*iSbD&(pY@zBTvKJ-rRsXD>-vGzAD`|^pI7hVejv=G)nLyeF)y!<>R(n8f!^P3+wW8pLnnL zGBHvMQD9d8P!G?g%##mjIrI>JprJ7$k{yz_cC1|N$Ca297oyEw$K{bxSj-1qTI?M7 zBNxWFwa=hp*plnU4&JuX&pJ#jQF>Dg3w7jWLg>EzDR%?`!UdGe*qEqx?su0K|NLG} zxnn;mB!t%B)rka1E(66N2^B9ZuRCgerUkV>8?n}hYsN{x=15ud# zS_qvu$GWk$30KLRuKpBawMM>LHE(s#EzZ%Gla-n6As_F+MFq#o_eZTg-m@ncDTst` zoQ9V107seCJ;3|!o&~$_$3_9D?zy?XKjq3U7XaKkT3LKufHx`Qj6Qq5v<3uKZn$?4 zQH2)auxnv)juFlDheiiRPaif=&&7BcZYb(x$GyKBi!ta`hdg3cd1pu}p0#0(C_;au z!an`a?y!Os9Vv|TItB_41f2Yl`aDyRhSXBIX~ey$NgVa_C#F6oT4j$6+UvT8^3Kf3>LA6p1Kc9r(37X27J zfT%k%()`IqCUZW@n#i{{WFo{ix*Jm_42-BQX!(JhNjx=@x?rd{phiTXve^F{Jp0~` zNcpSII}~Kv-OQ=X(^m8gaS&KORoG=I-+_u^FZ?wBVl^?O4#u74x2u^KmyQb`&>T@E47w6fW*G+ z_m*`y6W3Du@gYW;ibJKN$Mlk9NhZ@S)${A#Y?@-D6$AtbcrY#aV(Dw9Rix@eL?E5b{3DEm3iodf}3x#c8a? z(feb3RxN%ZAzd4>Qt?kZcF_3bV7-5?p}RXdz_L!*sw%t?Uk=l^&}J$m8DBpw&Dh!A zN%0&F3R4}#yE*h{pwuCDw0cx4ERdM88d)%Lun*AMYFU~+yruIm#yaAg1l0xCm)S`g zB4-^3BXO%aJ-Tm@qhA(&Xgk^WZW~peeBfyO1`2=G@SbBDFNLP=q=*7;^;2#33FIB6 zj_j{)1w>i|e*U&o$h~o(mPAtLJ@zq$V0C&>8V5Lro0SI)EIA)$y4^_ZlLCeC~{TeGdk7JC} zmKc@nBYH+zWS{zmaU#gXIaO-cG#{G8P_U|)dpj{k31-VRkWnmnJ7^li~(}d zAm4?*Wh!SIdjp)Ju7Z-LFgMdGCl6qKbqMZ2W|c7uCcoV7G6sBlze+O}JyT+kNe&*% z8vn3abe!N&x&3fp3G=wmkkNK7;n=VnSk$p;B6CuIF`&q940iyq(11WY!T7G5``%9w zxOxb1Ah$he?&_(yl=C(Ri+`nteA$8X{<9L$OeBQt_gmFxi;ULNh@7}YDilsHoLXojz2as}=Ne; zZ6qCeiraso%$JD>v;k;P(l7lz%YT}KVWC-s;Ng_Y?F^I@M?UIE z4I62{04Q=voI!zJVEDXVy}0RJRfdx8nJ^EIsXB{xWvB!PNKqLc6eroLJk=zvUw<-5 zyjMA7w6v{P9pAym3@^*?YKiOIF-tmWH>i64xjL z`K=n4F(KuK0V`5IBCH{wCs1i$hFj>jb&_T2OmJjh0dTP5m6-Sk6uum{ib<>yB$qz- zH^+_I4<`WmV;*bs3)N{gEI;=xXWp4A{W9aO{HfMw_b(v;7OYgRP~h}wCbD~s=&1Xv z9Izij$-s3*!ZLCDVD8@<8eL*`oI)t(z(A>?fLGk;^EzOcy*`KJt$}<< zA-(351`tY-sZowpDhD|8@UDD%%Ck8SqH?B141scXw9hqCKXn^oO;de)>srdJYZNJt z5-F$QlyH*|H|>#)`yr7{KNeTH#~ITDqw}ab*1lhfhY}=)>qk6H}@dyCmWe4;xot(>q{1(3DzQRoVa!&{MdE|K>iV_0p z0{^0xbR_V~Q?H(){q~yYY_cY`_yDA>IsQZ2NJ!Q{2c&a^eBKQkZefN)Oj(zESByx1y zjxqkHHoFXw5tZy0!s)koj57BV2tIhfdb$Kw2AdKB^YR8& zZxo?>ZJ51UtR$v+(x}~2o%2v*h=S31!W9DB9g&yZ-#ghGc9kkjZOGH|t$q87yfOU~ z%KO=Z`9v?a;f+<>!tbqL=Nn_t8B?DsmOR4An!+)ro=vjN}X<`7q=(ULlQli{5=cj1Xo ztF<%5>_$>rHTP@MrEFvH=JRn`-&Y0dru{1>np|^qylHS zXO&oP$JI>(uAl|EDcZ;2s zcAA=KnM6WIovjB9hi=?8{M2sc4~&5(odw*qZ=a)y#GuRfRX?6c@@vHdsXxphLxX!ht;+dIvO|`|jdy7r^NXe$v9ZmHU&HrOm%&hHeOd0( zkJE0L5hDaYznV0bGQ>{u3Z)tz_2)~hM@OPlr7(7R76(XLTEnJk+~lw8VhsWZYpx`_ z$1Mwm8mZ@FX{X0cT-(-0El$8I51P9A?lj04E~7UY~1A=zcwPOTPr3~&zpm58j$8H9Jkji@?1?Nxq+M) ziIz|MUR=maOmZEs23Iy+=JU}6m**N|fic_LM+~ z+RxLOi5wG{&Y@(jKY8J}c_TuSWt1pyVYVeteB`I-_*XZ^hc&>4KdfSW3A~PZE{&sY zvKPVDx3g`3?a5&PKmTp8Z5UvB4Tc+qrs)XWAss@tiuAv`A&?vJd!5*jYlc25zausQ zOcA^SJ$tCgOeU#Min!y!gURJSJWZQ`4P-f-S2ciNYcDQskyuN@d?j3cEfc+4nt&#r zV$&ODr+O3i9$t9bFgyPmq&58OmT^e)5*^O}vU1_gx>Wm&NnV|s3@6slk+ase?)F&B zisQNtBRvS`m`xZ!vjSqlN1{NC!dhhmm~iF5v$WJVLNFAir* zth!3pTT7Ia*;q>lxS=Oo$ln=o4|O%sQbBB_aXssdcgoZlT>M-8??R0f!}hJU-3&ev z-~QRQnsGr4=?y<|&++I0c68bn9hO>CgNh52i0oQ>O*3{eJNb8B&$>;ZTW9hSecN9V zOW{C@MkDe@(8JjFb2kOENrgq!&APmC@r*^?Jdyua+zLH8}aK zp$j#SRJ9&HMB{ORLfjGlwwNK$y_#{pTG--3Av7(tq;1FtZyDDAsa!rwVu<|0G7kA=qzeX0Xh9ST6Zs zebgwp-`hni=hH`(VPc>vr?qXqtmZyC-e&CEqs+pXfaL_S5u`+uvo&*8b5c%o){IJu zHFR}{sn!<2nWDaCiOe?0(`zA5xM5J9RWYT;Ce6M`I^yk^YKgM3$NVhU>fdg;j<^Ugbu&0gdlqr_ub`G?UIV zvMZk%R5HfZWp%<|?db>eXR=v;feFMTR+5#`b;FSkPpz_B{oX?=8U1Dbk)=s&HgYhv zX*F}5!H}&D)$6EXcaz~S3;mrKm=Gj!qMHF2DDk?I-~6;DJ#IX>G$7s0YJ8Ok_>Ue; zNE+Mh)>^xuu5WLhr!9U}4yp~^bg-B>N^}8=k5#|ut0FMr{Jrx+9>^*N@j$jX8lJf!-L8hYm_VS-xHhOGt^rYL zzc&RANZOUUMcup!99j{s6IA@=Avp*X^s6X-)sv0Tr0Tv2r>ca2p-;pp9tebQ)c847 ziJo~bdRQaLt_yoD&zT$t1xiS=Iw)Fg#gem5FTFro*w{knGt|K#5X-OTfof~O<#kSE znm(HMo`BApz;tmy>3`Hkt9!AzZ1Q#J42MyRNS5I1NP3Zgb;Smt7WtoAu;{CCd@(-j z1H}fb9rDJDrTvWlA z=PmW87e<@BXKD>-Uw?DDEqxpy2U;y78!%`k8XO1P%*p&3Rco<2#I_{h|6UXbDq!0G lcP9V;etG?Wt|9UE4h3d&8K3lo1=w^TB{_B3>Q`^y{}=Amd?o+@ literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/a07/preparation-error-ko.png b/plans/audit-2026-09-23/artifacts/a07/preparation-error-ko.png new file mode 100644 index 0000000000000000000000000000000000000000..7e8208cf10238f659675b91c113ed9195430305d GIT binary patch literal 129477 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

8TNKIWa|lsJA!QsL)$ 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 DodQ&g literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/a07/preparation-partial-ko.png b/plans/audit-2026-09-23/artifacts/a07/preparation-partial-ko.png new file mode 100644 index 0000000000000000000000000000000000000000..d7c14a3c612f46eaaf7dd8db91e1388002d7340c GIT binary patch literal 102028 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 z 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 literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/a07/source-freeze.json b/plans/audit-2026-09-23/artifacts/a07/source-freeze.json new file mode 100644 index 00000000..683cb308 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a07/source-freeze.json @@ -0,0 +1,35 @@ +{ + "baseline": "c0907712429f5aa7a2ffbaf50332e665409dff7a", + "files": { + "lib/data/data_sources/early_start_session_local_data_source.dart": "a82657e5337aab46d99868f031156c71d56d823482682a65f7360ad2b227dfae", + "lib/data/repositories/schedule_repository_impl.dart": "3071ca881e33da7fb85684ee0898fdd5e5066051cf37e8b08eedd0acc361df54", + "lib/domain/entities/schedule_start_rejected.dart": "87b12570f7e622fd4490d7b46b9e9d2d24d0ce6ed192864888280ce614b0ecbc", + "lib/domain/repositories/schedule_repository.dart": "144ca813485e542b7a16e94780bbe85593a67f6a87a43391e351b62c8c69d0a2", + "lib/domain/use-cases/finish_schedule_use_case.dart": "2a4897f2cc28abaa3c8f50c252d8c8d9525a0c35a7e2bb55a8c36ef31089b7ac", + "lib/domain/use-cases/schedule_preparation_session_use_case.dart": "66426b3eff70d8d9dc78912dcaf782d60fa3e5e8c66a90a95a509d9f7f23c54d", + "lib/l10n/app_en.arb": "acb4d5cf83dc5132b40857e503677398ccca4d849a75454771b54995a5636c4a", + "lib/l10n/app_ko.arb": "25acbfed2188139d78f7b5f3fa14bdecddce66f805eaaaaa78e0f839550590f9", + "lib/l10n/app_localizations.dart": "8af7a8251e5931b5a6e2594c993471bd0f880d5d4d0e8d12a184b5e1c5717dd3", + "lib/l10n/app_localizations_en.dart": "9982c9681e6d4a3f6d5f0520fee91dbcb4109d9b4daf4d81c42b48cc9e8c704b", + "lib/l10n/app_localizations_ko.dart": "7c925f9487e86a3a64160ae34a1dfe744c19eba841ddba9a5a5040709d975d58", + "lib/presentation/alarm/components/alarm_screen_top_section.dart": "a4a3565323291193f8057cf417fd3916c71d46551b0f247211ddc7f745a061de", + "lib/presentation/alarm/components/preparation_step_tile.dart": "96a68057dc3b425d5be0f822ae0343113a06d51ac438181349dbc05da3305bcb", + "lib/presentation/alarm/screens/alarm_screen.dart": "c19dc1ca73f4377382fa4b236ab6a7c3958392b49a6e1f01079aa5ae3b2ebbf8", + "lib/presentation/alarm/screens/schedule_start_screen.dart": "903e90a4ca9e7cc541d4ed07309062f548ac44104bced8ccc95576dcb9a5b2ba", + "lib/presentation/app/bloc/schedule/schedule_bloc.dart": "a44e99a352f540d92d2d073f2ebe8529b589e5bf7e8c66b361c32f67493ad9e4", + "lib/presentation/app/bloc/schedule/schedule_event.dart": "3429adf54c131ab193e1a81da556c34cf38fd302c9b4117554b0b3f6460dc07e", + "lib/presentation/app/bloc/schedule/schedule_state.dart": "ac4926f5ddb2813638115c8a1aa6bf41f2b10c259b07c8f40bf6379596847a7c", + "test/data/repositories/schedule_start_transaction_test.dart": "4b42aebf7f5e7600c8eff235a31f945eedeb98ba6a3c5bc87c27f0e7fbbb8171", + "test/domain/use-cases/schedule_mutation_use_cases_test.dart": "e5ccf01f01226850377e864e5d9012c8be0983772873d23688ad54ee6ca57213", + "test/domain/use-cases/schedule_preparation_session_use_case_test.dart": "f434dabb6a6c6f5154d85d026d0dd81281dbdc3b9a70df4cd772a03a43e85559", + "test/domain/use-cases/timed_preparation_use_cases_test.dart": "08fc5ce01227d3e1a79ea17220aa08cecad9521c109c81c5b2a4a2896e4ec5d2", + "test/presentation/alarm/screens/preparation_flow_widget_test.dart": "889b8c1b42a5932c6ddd46b75526823701990f55e41930ed586c2b017fdc6e80", + "test/presentation/alarm/screens/schedule_start_commit_widget_test.dart": "f8e872f748bb4eb14a96a545240fdc072ecbe9c9fc3b83b8a8c63dbf524bbdc9", + "test/presentation/app/bloc/schedule/preparation_start_recovery_test.dart": "1af89ef608c92f1015996fee68e9e7e82b7615fc75d3fd70dee9ed52c307416d", + "test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart": "a8ff3397cadf360dca302f02b627b339fe8e7ffc7878e5ab7730ce8c03955e43", + "test/presentation/app/bloc/schedule/schedule_bloc_test.dart": "0c0ec915425269a620cfe50d3baecefb6fa17e361042ae0a4a29951a973d614b", + "test/presentation/shared/router/notification_launch_widget_test.dart": "08de5fe0ae1d328d22529228fe245a15024cd18311c4942e8bce57c241691c46", + "tool/qa/capture_preparation_start.dart": "170ea3f55f54030b2f7b6159c053704f15fd2d6a3c74d6c85518b337114fed45" + }, + "source_manifest_sha256": "dfedcb18495e5fc0d89bb659d24832ce70fb32a577b80f1a30df6c92ff9111de" +} diff --git a/plans/audit-2026-09-23/issues/A07.md b/plans/audit-2026-09-23/issues/A07.md index 1447ff1f..3820c6cf 100644 --- a/plans/audit-2026-09-23/issues/A07.md +++ b/plans/audit-2026-09-23/issues/A07.md @@ -156,3 +156,90 @@ ## 작성 시점 상태 read-only 조사와 실제 3문답 및 보충 질문/정정을 완료하고 이 이슈 본문만 작성했다. 제품 소스/테스트/공유 tracking을 수정하지 않았고 테스트 실행·GitHub 게시·커밋은 하지 않았다. root가 본문 게시와 read-back 후 정해진 우선순위·의존성에 따라 구현한다. 이슈 생성과 실제 해결을 구별하며 아직 완료 처리하지 않는다. + +## 2026-09-24 구현 및 독립 리뷰 결과 + +구현 커밋 `d73c7863a01343004ccadcd72064b563c464dd0d`에서 준비 시작 상태·최초 startedAt·preparationFrozen·dataRevision을 실제 Drift transaction으로 함께 저장한다. 같은 회차 반복/동시 호출은 최초 durable 시각과 한 번의 revision 변경을 보존하며 완료·삭제 회차는 시작을 거부한다. 실패한 flight는 제거되어 같은 화면에서 다시 시도할 수 있다. + +DB 최초 시각과 Preparation Run 시각은 구별한다. 유효한 runtime snapshot의 동일 run은 기존 시각과 skip events를 유지하고, requiresConfirmation/변경된 fingerprint를 확인한 명시 새 시작은 새로운 run 시각을 사용한다. DB startedAt 하나로 runtime을 자동 복원하지 않는다. repository가 반환한 최초 시각은 별도 durableStartedAt receipt로 전달한다. + +실제 시작 버튼은 DB 저장 결과 전 화면을 이동하거나 prompt owner를 소비하지 않는다. DB commit 전 오류는 입력/화면을 유지하고 한국어·영어 오류와 재시도를 제공한다. commit 후 marker/snapshot/알림 취소의 실패는 시작을 취소한 것처럼 표시하지 않으며 준비 timer를 유지하고 지속적인 부분 실패 배너로 알린다. 명시 재시도는 같은 run의 최신 projection을 사용한다. 같은 일정의 stream 갱신은 pending 안내를 없애지 않고, 복구 중 연타는 같은 작업을 중복 생성하지 않는다. 부분 저장 실패 동안 timer/skip은 최신 메모리 projection만 갱신해 매 tick마다 디스크 쓰기를 자동 재시도하지 않는다. + +세대 교체·finish·화면 이탈 뒤 오래된 결과는 새 runtime과 navigation을 변경하지 않는다. 공통 alarm owner의 실제 I/O가 끝날 때까지 소유권을 유지하고, generation 변경 시 이전 캐시 참조를 정리한다. DI dispose는 shared listener를 제거하며 대기 중 native/DB Future를 강제 종료한 것으로 간주하지 않는다. SharedPreferences early marker 저장의 false 반환도 실패로 전달한다. + +### 실행한 검증 + +- 수정 전 실제 Drift 회귀3개에서 revision 실패 후 부분 상태, 중복 시작/revision, 완료 회차 시작 결함을 재현했다. +- 최종 집중56개 통과. 실제 Drift transaction/revision failure·singleflight·retry·DB 최초 T0/유효 runtime T1/재확인 T2·marker/snapshot/cancel 부분 실패·최신 skip·finish 경합·generation·dispose 경계를 포함한다. +- 실제 SQLite 저장 오류를 실제 시작 버튼까지 연결했다. 실패 후 동일 화면 재시도, 저장 대기 중 버튼 비활성화, route 이탈 후 뒤늦은 이동 방지를 검증했다. 고정 sleep 대신 실패/commit/cancel 도달 latch를 사용했다. +- 동결된29파일에서 전체 **944개 통과**, coverage **88.67% (13289/14987)**, analyzer **No issues found**, Python 정책11개·Android source manifest·generated/local-only 정책·diff 검사 통과. root가 모든 파일 및 커밋 blob 해시를 다시 대조했다. +- KO/EN,430×932,글자200%에서 오류/partial 화면4개를 실제 widget/번들 글꼴로 캡처하고 직접 확인했다. 실제 목록 스크롤·skip tap·다음 단계 hit-test도 통과했다. 캡처의 receipt는 합성 fixture이며 실제 DB 버튼 회귀와 증거 수준을 구별한다. +- 200% 검증 중 기존 graph/step tile의72px/22px overflow와 시작 버튼 글자 잘림을 확인해 내용 기반 최소 높이로 보완했다. U08 전체 접근성 완료는 아니다. 영어 화면의 기존 한국어 시간/skip 문구는 U05 잔여로 남긴다. + +근거: `plans/audit-2026-09-23/a07-final-validation.json`, `artifacts/a07/`. 원본 로그 해시와 저장용 후행공백 정리본 해시를 구별한다. source freeze manifest SHA256은 `dfedcb18495e5fc0d89bb659d24832ce70fb32a577b80f1a30df6c92ff9111de`다. + +원격 CI, 실제 Android/iOS lifecycle·native delivery, 병합은 아직 이 결과에 포함하지 않는다. 수용조건이 남아 이슈는 OPEN이다. + +## A07 구현 중 실제 추가 리뷰 문답 + +## DB 최초 시각과 Preparation Run 시각 + +root: +> root독립리뷰에서실제결함발견: _startEarly가 _scheduleRepository.startSchedule(...startedAt:run.startedAt)의 DateTime 반환을버리고 marker/snapshot/receipt에run.startedAt를계속사용합니다. DB가이미시작됐거나새screen/runOwner/새instance재시도면repo는기존firstStartedAt를반환하므로DB/run/timer시각이달라집니다. authoritative committed timestamp를반환값에서받아후속모든단계·receipt·projection에사용하도록수정하고, 실제DB이미startedAt=T0인상태에서새usecase/owner가T1으로재시도해T0보존하는회귀추가해주세요. _runs owner교체중이전commit과새시도가경합하는case도같은불변식. 신규첫시도실패후동일intenttimestamp유지와모순없도록candidate vs committed시각구분. + +implement_a07: +> 이 지점은 A07 Q2 보충정정/Q3 최종 계약과 구별이 필요합니다. 계약: schedule.startedAt 최초 durable 사실은 보존하지만 A11 재확인 후 명시적 새 run은 독립 T1, DB T0만으로 active run 복원 금지입니다. 현재 runOwner 교체는 새 explicit intent로 취급합니다. 권고 수정: repository 반환 T0를 별도 durableStartedAt receipt로 유지; 유효한 동일 runtime/same run retry는 저장된 run.startedAt T0, 명시적 재확인/새 prompt의 새 run은 T1을 사용합니다. 새 usecase+DB T0만 있고 runtime없는 경우는 자동 T0 run 복원하지 않습니다. 이 구별 유지하면서 '유효 snapshot의 동일 run 재시도'와 '재확인 새 run' 각각 실제 DB 회귀를 추가하겠습니다. root가 의도한 새instance T0 보존은 유효 runtime 있는 동일 run에 한정하면 확정 계약과 일치합니다. + +root: +> A07 Q2 보충/Q3를 재확인했고 당신의 구별이 맞습니다. 앞선 root의 DB 최초 시각을 모든 새 run에 강제하자는 제안은 철회합니다. schedule.startedAt은 durable 최초 사실 T0, 유효한 동일 run 재시도는 해당 run 시각 보존, A11 재확인 후 명시적 새 run은 T1로 유지하세요. DB T0만으로 active run 자동 복원 금지입니다. owner 교체 자체가 새 run 의도라는 근거가 되지 않도록 실제 explicit start/reconfirmation 경로를 확인하고, 유효 snapshot 동일 run 재시도와 명시적 새 run을 각각 실제 DB 회귀로 구분해주세요. 이 실제 리뷰 질문·답변·철회도 최종 A07 이슈 보충에 남기겠습니다. Flutter 독점 유지하며 검증 계속해주세요. + +implement_a07: +> 시각 구별 반영했습니다: owner 교체만으로 새 run을 만들지 않고 유효 snapshot이면 기존 run 시각/skip events를 재사용하며, requiresConfirmation 또는 fingerprint 불일치면 명시적 시작 시각으로 새 run을 만듭니다. repository 반환 최초 시각은 별도 durableStartedAt receipt로 보존합니다. 실제 DB T0 + runtime T1에서 유효 재시도와 재확인 T2를 분리한 2개 회귀를 추가했습니다. 기존 UI 3개 실패는 로딩 없는/외부 async zone fixture 문제로 수정 후 통과했습니다. + +## 부분 실패 표시의 수명 + +root: +> root 중간 리뷰: _onUpcomingReceived가 같은 active schedule의 stream 갱신에서 ScheduleState.started(...)를 새로 만들므로 hasPendingStartRecovery/isRecoveringStart가 기본 false로 사라질 가능성을 확인해주세요. DB commit 후 marker/snapshot/cancel partial → 다음 unrelated profile/schedule emission에서 pending 배너가 조용히 사라지면 안 됩니다. 같은 run refresh 중 pending 보존/실제 retry 성공 때만 해제 회귀가 필요합니다. 현재 WIP라 이미 수정 중이면 결과만 알려주세요. _runs fingerprint/validated snapshot 변경 방향은 확인했습니다. + +## 실제 UI 검증의 완료 경계 + +root: +> 추가 리뷰: schedule_start_commit_widget_test가 실제 SQLite 대기용 40ms/20ms sleep을 쓰고 있습니다. 고정 sleep은 느린 CI에서 결과 경합하므로 observable DB/event completion latch 또는 명시 condition을 bounded watchdog으로 기다리는 helper로 교체해주세요(tester.runAsync 유지 가능). 제품 _Cancel.barrier 도달을 별도 completer로 관찰해 버튼 disabled/route-exit 순간을 확정하세요. 실패 UI도 실패 Future 완료+frame로 확인. broad pumpAndSettle은 최종 정적 화면 외 사용 최소화. UI 텍스트 신규 오류/partial배너는 KO/EN 200% 렌더/overflow와 캡처도 남겨주세요. + +## 새 메모리 캐시 수명 + +root: +> 추가 작은 검토: use case singleton의 _runs/_projections/_starts/_epochs는 generation이 key라 오래된 결과가 새 run에 섞이지는 않지만 reset/restore 이후 이전 preparation 원문/action snapshot 참조가 계속 남을 수 있습니다. 기존 gate listener 또는 다음 public operation capture 때 이전 generation map entry를 정리하고 실제 진행중 Future owner의 안전한 완료는 유지할 수 있는지 확인해주세요. 별도 coordinator/무한cache 만들지 말고 최소 정리만; A09 전체runtime reset을 선행 완료했다는 주장 없이 A07 새 캐시 수명만 다루면 됩니다. + +## listener 수명과 테스트 관찰 + +root: +> generation listener 추가는 확인했습니다. singleton도 getIt.reset/앱 teardown 시 _operations shared에 listener가 남으면 old repository/usecase 전체를 붙잡으므로 @disposeMethod dispose에서 removeListener 및 cache clear를 연결하고 직접 생성한 테스트도 필요한 tearDown을 맞춰주세요. live native Future는 dispose가 강제취소/owner release하지 않도록 유지. + +## 확대 화면의 기존 overflow와 범위 + +implement_a07: +> KO/EN 200% QA에서 새 오류 확인 화면은 렌더됐지만 기존 AlarmScreen의 graph 고정 높이(190, padding100)와 PreparationStepTile 고정 높이(131)가 각각 72px/22px overflow합니다(/tmp/a07-ui-capture.log). 신규 partial 배너 자체 오류는 아닙니다. A07에서 2개 기존 컴포넌트의 텍스트 스케일 대응 최소 높이만 조정해 전체 화면 캡처까지 끝내는 방향을 권고합니다(새 디자인/전반 U03 재설계 없음). 이 작은 통합 수정으로 진행하겠습니다. 추가 범위 정책을 달리 원하면 알려주세요. + +root: +> 최소 통합 수정 진행하세요. 새 partial 배너가 들어가는 실제 AlarmScreen 200% 사용 가능성은 A07 검증에 필요합니다. 해당 기존 overflow는 U08 선행 보완으로도 교차기록하고 U03(알림 설정 상태)와 혼동하지 마세요. graph/tile 내용 잘림·스크롤/배치 의미를 유지하고 수정 전 overflow 증거와 후 KO/EN 캡처를 보존. broader U08 전체완료 주장은 하지 않습니다. 59/28 focused 및 generation cache 정리 수령했고 최종 검증 기다립니다. + +root: +> root가 /tmp/a07-ui/preparation-error-ko.png를 직접 봤습니다. overflow exception 없는 start error 화면도 맨 아래 파란 버튼 '준비 시작' 레이블 하단이 잘려 보입니다(고정57 height +200%텍스트). 해당 버튼도 minHeight/내용기반 높이로 최소 보완해주세요. 스크린샷 렌더후 육안 확인이 필요합니다. 기존 캐릭터/레이아웃 디자인은 유지. + +root: +> 수정된 preparation-error-ko 및 partial KO/EN 캡처 육안 확인했습니다. 시작 버튼 잘림 해소, 새 오류/partial 배너 문구·재시도 표시 확인. 영어 partial 화면의 상단 시간 안내/단계 시간/skip 한국어는 기존 U05 잔여로 명시하고 이번에 전반 현지화 범위 확장하지 마세요. 200% 단계 카드가 finish 영역 아래 일부 가려 보이므로 실제 목록 스크롤로 skip/다른단계 접근 가능성만 widget 검증 부탁합니다. 배너자체는 화면 안에 들어옵니다. + +implement_a07: +> 추가 검토 반영 완료: getIt @disposeMethod로 shared listener 제거·새 캐시 clear, 진행 중 owner release하지 않는 회귀 통과. DB/취소 completion latch로 고정 sleep 제거. 실제 AlarmScreen 200%에서 KO/EN 모두 목록 스크롤 후 skip 버튼 tap 및 다음 단계 hit-test 통과했습니다. 새 오류/partial 화면 PNG 4개를 /tmp/a07-ui에 다시 생성하고 모두 육안 확인했습니다. 기존 EN 화면의 한국어 시간/skip 문구는 U05 잔여로 남깁니다. 최종 focused 56개 pass, analyzer는 테스트의 hasListeners 보호 멤버 관찰 2개 경고만 명시적 test-only 설명으로 정리 중이며 이후 source freeze/full 1회 시작합니다. + +root: +> 수령했습니다. root도 4캡처 중 KO error와 KO/EN partial을 직접 확인했고 새 문구/버튼 표시 이상없음 확인했습니다. 추가 root 변경 요청은 없습니다. 테스트의 보호멤버 상태 확인은 production API 확장보다 test-only 명시 ignore로 한정 가능하며 그 이유를 주석에 남겨주세요. 최종 freeze/full 진행하세요. + +## ARB 최소 diff + +root: +> rootWIPstat확인: ARB app_en1220줄/app_ko417줄 변경은전체JSON재직렬화로보입니다. 최종freeze전에원본정렬/들여쓰기/비ASCIIescape형식을보존하고신규키부분만최소diff로되돌려주세요(생성trackedl10n은필요한변경유지). 다른제품직접편집하지않고root문서만진행중. + +implement_a07: +> ARB 전체 재직렬화는 원본 git 내용을 복원한 뒤 신규 2키만 추가하여 최소 diff로 정리했습니다. focused 초기 81 pass/3 UI 실패 확인 중입니다. 실제 DB negative 3건은 수정 뒤 통과했고 singleflight 실패·동일 시각 retry, snapshot partial 후 skip 보존, 새 generation 독립 회귀도 통과했습니다. diff --git a/plans/audit-2026-09-23/issues/T02.md b/plans/audit-2026-09-23/issues/T02.md new file mode 100644 index 00000000..a1ca00f4 --- /dev/null +++ b/plans/audit-2026-09-23/issues/T02.md @@ -0,0 +1,214 @@ +# [P1][T02] 실행되는 네이티브 계약 테스트와 Android·iOS 물리 기기 검증 행렬 구축 + +## 문제와 결과 목표 + +현재 Dart 회귀와 네이티브 컴파일·APK manifest 검증은 존재하지만, 이것만으로 native picker가 실제 파일을 저장했는지, 설치된 SQLCipher·secure storage가 요구대로 동작하는지, 권한 변경·잠금·재부팅 뒤 OS가 알림을 전달하는지 알 수 없다. 이슈 T02는 **실행되는 native contract와 실제 OS 행동을 구별한 재현 테스트·증거 행렬**을 만든다. 기존 검증을 다시 만들거나 테스트 개수만 늘리는 작업이 아니다. + +- 우선순위 **P1**, 감사 T02. 전담 subagent `/root/grill_t02`가 `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`를 읽고 한 번에 한 질문씩 root에게 물었다. root는 사용자에게 위임받아 답했다. +- 조사 고정 SHA: **`7a6825f79a565c9b006f3ec57b837271b0e06ba4`**. 코드 근거는 이 커밋으로 고정한다. 병행 A07 WIP를 완료 소스나 실행 증거로 인용하지 않는다. +- 이 문서 작성에서는 제품·테스트·공유 문서 수정, Flutter/native test 실행, 기기 조작, GitHub 변경을 하지 않았다. 아래 새 행렬은 **미실행**이며 이전 실행 증거는 별도 출처와 한계로만 연결한다. +- Android·iOS local-only, 서버·로그인·cloud analytics 없이 진행한다. T01의 실제 앱 성공 여정, T03의 장애·암호문 검증, T04의 실제 이벤트와 독립 oracle을 재사용한다. + +## 감사 baseline 정정과 고정 근거 + +| 근거 | 확인한 사실과 남은 경계 | +|---|---| +| [감사 T02](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/docs/App-Improvement-Audit-2026-09-23.md#L115) | method channel 성공 mock을 넘어 receiver/manifest/picker/keychain/cipher/provider와 권한·잠금 화면을 구분해야 한다. 최초 감사의 다른 항목 상태는 최신 소스로 재판정한다. | +| [RunnerTests:8](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/ios/RunnerTests/RunnerTests.swift#L8) | A02가 placeholder를 malformed export argument 검사로 바꿨다. nil, 잘못된 bytes, 빈 bytes, 경로 이름, 잘못된 확장자에 실패 callback 한 번을 검사한다. 이를 아직 placeholder라고 반복하지 않는다. | +| [Runner scheme](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme#L50) / 고정 tree의 `android/app/src` | RunnerTests가 TestAction에 연결돼 있다. Android app의 `test`/`androidTest` 테스트 소스는 고정 tree에 없다. scheme 존재·Swift typecheck는 실제 XCTest 실행 결과가 아니다. | +| [Mobile Backup Verification](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/docs/Mobile-Backup-Verification.md) | A02 Kotlin 실제 Android/Flutter API 컴파일, Swift iOS simulator target 및 RunnerTests typecheck, Dart/widget 회귀가 기록돼 있다. 문서의 당시 “Android 전체 build 미실행”은 이후 APK CI로 보완됐지만 native picker·교차 복원·XCTest 실행까지 보완된 것은 아니다. | +| [Android Notification Contract workflow](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/.github/workflows/android-notification-contract.yml) | release APK 빌드, packaged/merged manifest, receiver 및 no-network 계약, artifact identity를 검사한다. 일회용 검증 서명이며 `device_delivery_verified: false`이다. 이미 있는 검사를 미구현이라고 쓰지 않는다. | +| [A06 CI 증거](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/plans/audit-2026-09-23/a06-ci-validation.json) | source `e6c1438e…`의 host 929개/88.79%, Android APK CI 성공이 있다. APK 검사 artifact는 merge SHA `0896d865d605a9b0966742f9172863b219a6ec5b`, digest `d0f64d4e914bbeb6dcaf7f37e9c62fdaa8f9b35b9b5fc0df673f8fc561c51007`. 이 과거 SHA의 성공을 T02 신규 native suite나 현재 물리 기기 성공으로 합산하지 않는다. | +| [Android manifest:52](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/src/main/AndroidManifest.xml#L52), [NativeAlarmPolicy](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmPolicy.kt) | 플러그인 ScheduledNotificationReceiver/BootReceiver와 boot/update/QUICKBOOT action이 추가돼 있다. Android full-screen 승인은 false다. 실기기 테스트 편의를 위해 이 값을 켜지 않는다. | +| [MainActivity:40](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt#L40), [취소 read-back:244](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt#L244) | 실제 plugin/native channel 등록과 payload sanitize, timing 권한 경계가 있다. Android에는 app-wide AlarmManager 열거가 없다는 계약 아래 reset 전역 취소는 false를 반환한다. known token 취소의 성공을 모든 OS 예약 부재로 확대하지 않는다. | +| [iOS capability:139](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/ios/Runner/AppDelegate.swift#L139), [observe/reset:280](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/ios/Runner/AppDelegate.swift#L280) | canImport 및 iOS 26 가용성 분기로 AlarmKit/fallback을 구분한다. 실제 manager 열거와 unmappedCount, 실패/unsupported 경계가 있다. 구버전에서 사용 불가능한 provider를 성공했다고 표시하지 않는다. | +| [Android BackupExportPlugin](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/src/main/kotlin/club/devkor/ontime/BackupExportPlugin.kt), [iOS BackupExportPlugin](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/ios/Runner/BackupExportPlugin.swift) | Android ActivityResult/ContentResolver write·flush·close와 iOS own staging write·sync·readback/export-as-copy delegate는 서로 다른 완료 경계다. 단일 callback/늦은 callback/cleanup의 native 런타임 검증이 필요하다. | +| [native DB opener:6](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/lib/core/database/open_database_native.dart#L6), [schema:54](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/lib/core/database/database.dart#L54) | background NativeDatabase에서 PRAGMA key를 실행하고 schema2/v1→v2를 지원한다. 고정 소스에 cipher_version 확인은 없다. D04가 실제 엔진·원자 migration을 소유하며 T02가 native 재현 실행에 연결한다. 평문 유출이 실제 확인됐다는 주장은 아니다. | +| [InstallationKeyStore:14](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/lib/core/database/installation_key_store.dart#L14), [LocalDataFiles:14](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/lib/core/database/local_data_files_native.dart#L14) | iOS first_unlock_this_device key 옵션, 실제 secure-store read/write/delete와 DB 파일군/backup 제외 경계가 있다. fake store나 uninstall만으로 실제 key 수명·보호를 증명하지 않는다. | +| [Android build:77](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/build.gradle#L77), [iOS project](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/ios/Runner.xcodeproj/project.pbxproj) | minSdk23, targetSdk는 locked Flutter 도구에서 해석, iOS deployment15다. toolchain/최종 artifact의 실제 값을 실행 manifest에 기록하며 최신 OS 출시를 근거로 지원 정책을 바꾸지 않는다. | + +[CONTEXT](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/CONTEXT.md)의 Local Data Store, Installation Data Key, Recovery Mode, Android Schedule Notification, Fallback Notification, Restore Preview와 [ADR0008](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/docs/adr/0008-use-notification-based-schedule-delivery-on-android.md), [ADR0010](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/docs/adr/0010-keep-delivery-enabled-with-fallback-notifications.md), [ADR0018](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/docs/adr/0018-encrypt-active-data-with-a-device-bound-key.md), [ADR0019](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/docs/adr/0019-schedule-future-notifications-by-platform-capacity.md), [ADR0024](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/docs/adr/0024-enforce-no-network-in-product-builds.md)를 따른다. 실행 방식·증거 분류는 도메인 용어가 아니므로 새 glossary/ADR를 추가하지 않는다. + +## 공식 근거와 읽기 한계 + +- [Flutter 테스트 개요](https://docs.flutter.dev/testing/overview)는 integration_test가 native permission dialog·notification 같은 UI를 조작하지 못한다고 명시한다. Flutter tree 밖의 조작은 native driver/명시 기기 절차가 필요하다. 문서는 현재 버전 기준이므로 이슈가 locked Flutter 3.44.4를 자동 업그레이드하라는 뜻은 아니다. +- [Android instrumented tests](https://developer.android.com/training/testing/instrumented-tests)와 [UI Automator](https://developer.android.com/training/testing/other-components/ui-automator)는 기기/에뮬레이터에서 플랫폼 및 시스템 UI를 실행할 경로다. 현재 문서의 alpha 예제를 그대로 추가하지 않고 locked Gradle/SDK와 호환되는 runner를 선택·고정한다. +- [Android notification permission](https://developer.android.com/develop/ui/views/notifications/notification-permission)은 API33+ POST_NOTIFICATIONS 권한 분리를, [alarm 문서](https://developer.android.com/develop/background-work/services/alarms)는 exact 권한·실제 OS 동작 경계 검토를 뒷받침한다. API/target별 정확한 동작은 설치한 artifact와 실행 OS로 판정한다. +- [Storage Access Framework](https://developer.android.com/training/data-storage/shared/documents-files)는 시스템 document picker 및 content URI 경계의 공식 근거다. [SQLCipher API](https://www.zetetic.net/sqlcipher/sqlcipher-api/)는 cipher 확인과 실제 엔진 계약의 참고다. PRAGMA 실행·단일 엔진 검사만으로 전체 보안 감사를 완료했다고 주장하지 않는다. +- Apple [XCTest](https://developer.apple.com/documentation/xctest), [AlarmKit](https://developer.apple.com/documentation/alarmkit), [Keychain 접근성](https://developer.apple.com/documentation/security/ksecattraccessibleafterfirstunlockthisdeviceonly), [export-as-copy](https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller/init(forexporting:ascopy:)) URL은 확인했다. 본문은 JavaScript 페이지였고 Markdown 응답을 이번 web 도구가 읽지 못했다. 따라서 이 링크를 본문/API 세부사항을 확인한 증거로 쓰지 않는다. 위 iOS 계약은 고정 repo 소스·기존 SDK typecheck 기록에 근거하며 실제 구현/실행 시 SDK와 공식 API 본문을 다시 확인한다. + +## 확정한 검증 경계 + +| 증거 종류 | 증명하는 것 | 증명하지 않는 것 | +|---|---|---| +| 소스·정적 계약 | receiver/권한/entitlement/등록·타입 연결과 금지 정책 | OS가 해당 경로를 실제 호출하거나 전달했다는 사실 | +| release artifact 검사 | 실제 packaged manifest·라이브러리/설정 및 artifact identity | 설치·권한 수락·실제 수신 또는 store 서명 | +| 실행된 native contract | 실제 XCTest/instrumented 코드의 입력·결과·수명·플랫폼 API 연결 | fake delegate/provider 행에서 물리 picker나 실제 provider 장애가 발생했다는 사실 | +| emulator/simulator 플랫폼 실행 | 명시 runtime의 서비스/스토리지/UI 동작 | 물리 secure hardware·잠금/재부팅·OEM 동작 전체 | +| 지정 물리 release candidate | 명시 artifact/기기/OS/권한/provider/상태에서 관측한 실제 행동 | 모든 기기·OS·배터리 상태 또는 초 단위 전달 보장 | + +1. 기존 manifest·native 컴파일·Dart contract를 유지하고 실제 native 실행을 추가한다. 유효한 RunnerTests를 다시 placeholder 취급하거나 단순 test 소스 존재를 실행 결과로 표시하지 않는다. +2. T02의 native contract는 실제 앱 plugin 등록과 메서드 이름/argument/result 계약을 통과해야 한다. 순수 parser 단위 테스트를 유지할 수 있으나 모든 결과를 fake 성공으로 채운 suite만으로 끝내지 않는다. +3. 정상 OS 행동은 실제 provider/권한/파일을 관측한다. 의도적으로 주입한 provider 실패, mock callback, fake clock은 사용 사실과 제한을 별도 기록한다. T03의 실제 장애 증거와 서로 바꿔 부르지 않는다. +4. source SHA와 CI merge SHA를 구별하고 실제 설치한 APK/IPA의 digest·서명종류를 기록한다. 이전 검증 artifact와 다른 로컬 build를 같은 것으로 취급하지 않는다. validation key build는 store candidate가 아니다. +5. 실패 후 무제한 재시도·조건 삭제·silent skip으로 green을 만들지 않는다. finite watchdog 초과는 실패/환경 blocked이며 반환하지 않은 실제 OS 작업의 owner를 해제해 새 작업을 겹치지 않는다. 지연 반환과 원 실패·후속 결과를 모두 기록한다. +6. 테스트 fixture는 합성 데이터만 사용한다. 키 값·비밀번호·production DB·복호화한 실제 사용자 원문·서명 비밀은 로그/스크린샷/artifact에 넣지 않는다. SQLCipher 동작 검증을 암호학적 안전성 전체의 보증으로 확대하지 않는다. + +## OS 대표 행렬과 격리 + +경계별 대표 실행으로 구성하며 모든 OS×기기×권한의 무한 곱집합을 만들지 않는다. 동일 실제 실행이 여러 행을 입증하면 하나의 evidence ID를 재사용한다. build-only와 실행을 분리하고, 하한 runtime을 구할 수 없으면 그 행을 pending으로 남긴다. + +| 범주 | 최소 검토·실행 경계 | 필수성 | +|---|---|---| +| Android 하한 | repo minSdk23의 build/설치/native 핵심 호환성. 현재 도구가 지원하는 actual runtime을 기록 | 자동 native 호환성 목표. build-only를 runtime pass로 치환 금지 | +| Android 채널 | API26 계열 대표에서 app master와 대상 channel 허용/거절 구분 | 자동/플랫폼 대표 행 | +| Android timing | API31/32 계열 exact access 허용/거절·설정 복귀, 예약 mode 확인 | 자동/플랫폼 대표 행 | +| Android display | API33+ POST_NOTIFICATIONS 허용/거절/응답 없이 닫기와 master/channel 구분 | 자동/플랫폼 대표 행 | +| Android release | 실제 resolved target 및 현재 지원 runtime, 물리 대표 1대 이상 | 핵심 권한변경·잠금·일반 kill·reboot·동일 서명 update·수신/탭/비공개 필수 | +| iOS 하한 | deployment15 build 및 fallback 호환성 | 실제 runtime과 build 결과 별도 | +| iOS fallback | iOS26 미만 대표 runtime에서 fallback 실제 등록·수신·탭 | 최신 iPhone의 fallback만으로 대체 금지 | +| iOS AlarmKit | iOS26+ 가용 runtime에서 capability·권한·native provider 및 fallback | 실제 앱/SDK availability 분기 확인 | +| iOS 물리 RC | 실제 물리 fallback과 AlarmKit 허용/거절·변경·잠금·재실행·update | 각 provider 행의 물리 증거 필수. 기기 부족은 OPEN | +| 추가 OS/OEM | QUICKBOOT 지원 OEM, 추가 OEM, Focus/DND·배터리 제한의 유한 변형 | 조건부 확장: 가용 대상/지원 여부와 not_run 사유 명시. 전체 OEM 확보가 무한 완료 조건이 아님 | + +- runner는 자기 소유의 disposable AVD/simulator만 생성·선택·초기화·권한변경·종료·정리한다. target ID, 앱 ID, run ID를 명시 검증하고 명령에 고정한다. +- 기존 `C8BA159D-A97A-458B-A5FC-B04A8B6F2848` simulator, 다른 QA 환경, 사용자 기기를 자동 선택하거나 지우지 않는다. 물리 기기는 이후 명시 지정된 QA 대상에서만 합성 fixture로 실행한다. 호스트 네트워크 설정을 바꾸지 않는다. +- 조사 시점 root가 파악한 환경은 Android 연결 없음·물리 iOS unavailable이다. 이 문서 작성에서 기기 탐색/실행을 다시 하지 않았으므로 현재 가용성을 확정하지 않는다. 실행 직전 read-back하여 가용성 manifest를 갱신한다. +- min/target/SDK/OS patch/모델·locale·timezone을 manifest에 고정한다. 새 OS 출시 또는 구 runtime 확보 문제를 이유로 지원 하한/SDK/toolchain을 임의 변경하지 않는다. +- Android ordinary process kill과 Settings force-stop은 다르다. force-stop은 별도 제한 관찰이며 그 상태의 전달 보장을 제품에 추가하지 않는다. OEM QUICKBOOT 선언 검사와 실제 OEM reboot를 구별한다. + +## Native 계약 실행 행렬 + +아래는 T02에서 구축·실행할 행이며 아직 통과 표시하지 않는다. Android instrumented/JUnit, iOS XCTest/xcresult 등 실제 실행 receipt와 assertion 위치를 남긴다. host 단위 회귀와 겹치는 부분은 native 연결 여부를 검증할 때만 확장한다. + +| ID | 경계와 입력 | 독립 판정 | 연결 | +|---|---|---|---| +| N01 | 실제 앱 native plugin/channel 등록 및 초기 호출 | MissingPlugin 없이 정해진 메서드/형식으로 한 결과, unsupported가 성공으로 바뀌지 않음 | A02/A12/A13 | +| N02 | RunnerTests 기존 malformed export와 Android 동등 native 검사 | 잘못된 bytes/name/path/빈 입력은 한 번 실패, picker·파일 성공 표시 없음 | A02 | +| N03 | export busy/동시 입력·늦은 callback·engine/Activity detach | attempt별 결과 한 번, 이전 callback이 다음 attempt·새 generation 성공으로 쓰이지 않음 | A02/A15 | +| N04 | Android 실제 ContentResolver export 성공·write/flush/close 실패 경계 | 전체 쓰기 완료 전 saved 없음; 소유 증명 없는 URI 문서를 임의 삭제하지 않음; 오류 주입 방식 명시 | A02/T03 | +| N05 | iOS staging write/sync/readback·presentation·cancel·scene disconnect/delegate | 해당 attempt 일치와 한 번 결과, background만으로 취소하지 않음, 외부 provider 내부 flush/close 관측 주장 없음 | A02 | +| N06 | own temporary file cleanup·중단 후 새 프로세스 | live attempt 파일을 지우지 않고 orphan 재시도; 이미 확정된 외부 saved를 tmp cleanup 실패로 뒤집지 않음 | A02/T03 | +| N07 | 실제 iOS import type 구성/취소·선택 결과 | public.data 요청을 실제 plugin 경계로 전달, 취소는 변경 없음; 선택 파일은 완전 인증 전 적용 없음 | A03/D05 | +| N08 | 실제 receiver/component·boot/update 처리 경로 | packaged 계약과 실제 대상 클래스 연결, plugin/native 역할 구별; 직접 receiver 호출은 실제 reboot 증거가 아님 | A01 | +| N09 | permission/capability/timing channel과 실제 OS 상태 읽기 | display·timing·native 지원 분리, Android full-screen false 유지, 권한 읽기 실패를 허용/부재로 추정 금지 | A13/A14 | +| N10 | native pending observation·cancel·unmapped/unsupported | iOS known/unmapped/unknown 분리, Android known token 취소와 전역 부재 구별; 실패 소유권 보존 | A14/A15/D03 | +| N11 | payload sanitize·initial launch/new intent·알림 탭 route | 최소 ID payload, DB/router 준비 지연 시 보존, 중복/invalid/deleted 대상 안전 처리 | A11/A12 | +| N12 | 제품 background opener와 실제 번들 SQLCipher | cipher_version 확인, 실제 encrypted 파일 재열기·wrong key/일반 SQLite read 거부. 키/원문 dump 없음 | D04 | +| N13 | 합성 역사 encrypted v1→v2→close/reopen | 독립 역사 fixture 관계/값/schema/version/FK 보존, 제품 migration 사용; 실패/중단 확장은 T03 재사용 | D04/T03 | +| N14 | 실제 OS secure store create/read/relaunch/delete | 설치 key 비노출 비교로 동일성/삭제 확인, missing/corrupt/read-failure 구별, 오류가 자동 새 key가 되지 않음 | D01/D03 | +| N15 | DB 파일군·tmp의 platform backup 제외 및 reset 후 재열기 | Android final backup rules와 iOS URL 속성의 실제 적용, 새 세대에 old DB/key/runtime 재노출 없음 | D03/R09 | + +N02는 현재 iOS 소스가 존재하는 항목이다. N04/N05의 오류를 native fixture seam으로 주입할 수 있지만 그 결과는 실제 OS provider 고장과 구별한다. 새 seam은 좁은 외부 경계만 대상으로 하며 제품 cipher guard를 끄거나 별도 성공 workflow를 만드는 방식은 금지한다. + +## 물리 기기 및 실제 플랫폼 행렬 + +정상 native 서비스 연결 일부는 emulator/simulator에서도 실행해 조기 결함을 잡는다. 표의 물리 필수 행은 그 결과로 대체하지 않는다. 기대 시각/실제 수신 시각·권한·provider를 기록하고 registry 존재·callback 성공만으로 전달 판정하지 않는다. + +| ID | 실제 실행 | 판정/증거 | 필수 연결 | +|---|---|---|---| +| P01 | Android 알림 master/channel 허용·거절, API33+ prompt 허용·거절·dismiss | UI status와 실제 OS 상태 일치, 거절 시 성공/정확 전달을 주장하지 않음 | A13/A14 | +| P02 | exact access 허용→회수→복귀/재허용 | 선택 mode·기존 예약 reconcile, inexact는 관측 지연을 별도 기록; 임의 초 단위 보장 없음 | A13/A14/A15 | +| P03 | 양 플랫폼의 예정된 Schedule 시작 OS 예약을 foreground/background 및 일반 process 종료 상태에서 실제 전달 | OS 배너/notification/AlarmKit UI·예약/수신 시각·provider·프로세스 상태, 중복 없음. A06 단계 알림의 suspend/kill 정시 전달을 뜻하지 않음 | A01/T01 | +| P03a | A06 active Preparation Run의 실제 periodic 단계 전환 | 실행이 연속 가능한 paused 상태의 fresh periodic 관측에서만 다음 단계 알림. suspend/kill 중 정시 단계 알림 보장 없음, resume/restore/catch-up 시 놓친 단계 알림 재생 없음 | A06/T04 | +| P04 | 잠금 화면에서 실제 전달, 일반/상세 알림 설정 전환 | 기본 비공개 문구·허용된 상세만, 과거 상세 원문 잔존 상태 및 cleanup 불확정 안내 구별 | [A05 #588](https://github.com/DevKor-github/OnTime-front/issues/588)/A11/U03 | +| P05 | 실제 OS 알림 탭으로 warm/cold 진입 | 정확한 일정, router/DB 지연 후 한 번 route, active run 덮어쓰기/자동 중복 시작 없음 | A12/A07/T01 | +| P06 | Android 실제 reboot 후 아직 미래인 예약 | 실제 다시 수신/탭, boot 전후 app launch 여부와 timing/권한 명시; receiver 직접호출과 구별 | A01/A14 | +| P07 | 동일 앱 ID·호환 서명·올바른 version 증가로 실제 update | 데이터 보존·미래 예약 전달·탭, MY_PACKAGE_REPLACED 실제 경로와 업데이트 전/후 바이너리 identity | A01/A11/T01 | +| P08 | iOS26 미만 fallback runtime | 실제 fallback 수신·탭·권한 UI, deployment/build 성공만으로 대체 금지 | A12/A14 | +| P09 | iOS26+ AlarmKit 허용/거절·회수·재허용 및 fallback | 실제 provider 전환·관측/취소·중복 방지, native 거절을 일반 알림 허용으로 추정하지 않음 | A14/A15 | +| P10 | 물리 iOS fallback와 AlarmKit의 잠금·일반 종료·update | 각 provider별 실제 OS 수신·탭·비공개 문구 및 권한 상태 | A11/A12/A14 | +| P11 | 실제 reboot 후 최초 unlock 이전 및 이후 secure storage 경계 | locked/unavailable과 missing 구분, 기존 key/DB 보존·자동 key 발급 없음. 실제 실행 가능한 lifecycle에 한정하며 OS가 앱을 실행하지 않은 상태를 key 접근 테스트로 치환 금지 | D01/D04 | +| P12 | 실제 로컬 native picker export→같은 파일 선택·전체 인증 | 파일 SHA256·cutoff·사용자 결과·freshness, 저장된 파일이 유효하고 source generation 일치 | A02/A03/T01 | +| P13 | native picker 취소·동일 이름·중복 요청·background/복귀 | 취소 시 freshness/DB 불변, provider 이름 정책 기록, 중복/잘못된 파일 삭제 없음 | A02 | +| P14 | Android Activity 재생성/iOS scene disconnect 및 export 중 프로세스 종료 | 미확정 성공 추정 없음, late callback 격리·own orphan 정리·재시도; 확정 saved 보존 | A02/T03 | +| P15 | 실제 또는 명시 fixture provider/staging 오류 | platform별 소유 오류 경계와 원인, 불완전 파일 안내/기존파일 보존, fake fault와 실제 OS 오류 구별 | A02/T03 | +| P16 | Android→iOS 및 iOS→Android 각 1개 실제 파일 전달·복원 | 전체 durable 필드 독립 비교, runtime/OS 권한/키 미이전, preview 후 별도 확인. 암호문 fixture digest만 공유 | A02/A03/A09/T01 | +| P17 | 양 OS 실제 번들 encrypted DB와 key 수명 | background opener 실제 cipher, v1→v2 재열기, 잘못된 key/read 오류 Recovery; key를 로그로 꺼내 증명하지 않음 | D01/D04 | +| P18 | 명시 reset→실제 프로세스 재시작·OS 재관측 | 이전 key/data/runtime 접근 불가, unknown/미완료 cleanup은 partial journal 유지, 외부 backup 보존 | D03/A15/T01 | +| P19 | clock/timezone 변경·capacity 경계 후 resume | A10의 시간 의미와 A14 관측 기반 reconcile, 실제 가까운 예약 우선·한도밖 보장 없음 | A10/A14/ADR0019 | +| P20 | force-stop/OEM QUICKBOOT/Focus/DND/배터리 제한 조건부 실험 | 정확한 조건·가용성·actual/제약 기록, 없다면 not_run. 정책 우회/무조건 전달 보장 없음 | A01 및 원 기능 | + +P12–P16에서 **로컬 document provider**가 기본 보장이다. 사용자가 명시 선택한 외부 provider는 OS 작업 경계이며 OnTime network client/권한을 추가하거나 cloud sync 완료를 주장하지 않는다. iOS 앱은 own staging write/sync/readback과 export-as-copy delegate를 관측한다. 외부 provider 내부 flush/close를 직접 관측했다고 쓰지 않는다. Android는 자신이 연 ContentResolver stream의 경계를 검사한다. 공통 최종 oracle은 저장 파일 재선택·완전 인증이다. + +P11/P17의 key 접근 불가를 “key 없음”으로 바꿔 새 저장소를 만들면 실패다. iOS uninstall만으로 Keychain key 제거를 추정하지 않는다. 잘못된 key·plain SQLite 거부는 강한 동작 증거이지만 side-channel·암호 알고리즘·모든 OS 취약점까지 감사한 결과는 아니다. DB의 물리 바이트 전체 동일성 대신 T03/D04가 정의한 논리 데이터·schema/version·소유 파일군 보존 기준을 재사용한다. + +## 구현·CI·증거 산출물 + +1. T02에서 재현 가능한 native test entrypoint, fixture, 실행 manifest, 결과 schema, native UI/수동 절차 및 증거 경로를 둔다. Android instrumented target과 iOS 실제 XCTest 실행을 구성하고 성공·실패 실제 결과를 확보한다. 실행되지 않은 예시 명령을 성공 로그처럼 적지 않는다. +2. 필요한 최소 native driver를 locked 도구와 호환되게 선택한다. Flutter E2E 전체를 새 native suite에 복제하지 않고 T01 native UI bridge를 공유한다. 프로덕션 데이터 export/키 출력용 debug channel을 추가하지 않는다. +3. 기존 Flutter analyze/test/coverage와 Android packaged-manifest CI를 재사용한다. **R02**는 native/mobile build·job 배선, **R04**는 동일 검증소스와 배포 연결, **R01**은 required checks, **R09**는 최종 release artifact/네트워크 경계를 소유한다. T02의 actual native test 결과를 이 경로에 연결하며 compile-only job으로 대체하지 않는다. +4. native contract의 결정적 행은 PR 자동 검사 목표, 오래 걸리는 OS 전이/물리 RC 행은 명시된 release validation 단계로 분리한다. 필수 행을 누락하거나 skipped job을 전체 성공에 합산하지 않는다. CI 연결 전에는 로컬 suite 존재만으로 원격 gate가 적용됐다고 하지 않는다. +5. 결과마다 `caseId`, `sourceSha`, `mergeSha`(해당 시), `artifactDigest`, build mode/version/build, signing 종류, 실제 installed app ID, OS/patch/model/target ID, toolchain/SDK, locale/device/schedule timezone, run/fixture ID, provider/display/timing 권한, lifecycle, expected/actual, 실행 시간, driver/injection, evidence/assertion 경로, `pass|fail|blocked|not_run` 및 사유를 남긴다. +6. XCTest xcresult/JUnit 등 native 원본, 실패 로그, 합성 화면 기록, 실제 파일 digest 및 재시도 전 원 실패를 보존한다. 권한/기기/driver 오류와 제품 assertion 실패를 구별한다. flaky 실패를 덮는 rerun만 남기지 않는다. +7. 테스트 runner가 소유한 임시 데이터·기기만 정리한다. 프로세스 timeout 처리와 제품 OS 작업 owner의 수명을 구별하고, 아직 작업이 살아 있으면 겹치는 재시도를 하지 않는다. 취소 가능한 fixture와 실제로 반환하지 않은 provider를 구분해 teardown도 검증한다. +8. matrix의 필수 native/platform/물리 행과 환경 가용성을 기계적으로 확인할 수 있게 한다. hardware/signing/구 runtime 부재는 구체 행 pending이며 다른 구현은 계속할 수 있지만 이슈 완료는 아니다. 기존 검증 링크의 SHA가 다르면 제한을 명시한다. + +## 의존성·실행 순서 + +- **A01 #583, A02 #584, A03 #586:** receiver/파일 picker 계약과 실제 전달·파일 I/O. 기존 코드/manifest/typecheck를 재사용한다. +- **A11 #589, A12 #591, A13 #592, A14 #593, A15 #594:** 최소 payload/privacy, 실제 cold tap, timing/display, provider observation/reconcile, 작업 owner. 구현된 Dart/native 코드의 OS 증거를 보완한다. +- **A06 #598, A07 #599, A08 #601, A09 #602, A10 #608:** 단계 전달·명시 시작·원자 저장·복원 generation·시간대. 실제 여정 기대값은 원 이슈 계약이며 T02가 편의상 바꾸지 않는다. +- **D01 #603, D02 #604, D03 #595, D04 #605, D05 #606:** startup/key 보존, 손상 store 복구, reset journal, 실제 SQLCipher/migration, bounded file input. 미구현 제품 결함을 테스트 fake로 덮지 않고 원 이슈에서 해결 후 동일 fixture로 재검증한다. +- **[T01 #615](https://github.com/DevKor-github/OnTime-front/issues/615), [T03 #613](https://github.com/DevKor-github/OnTime-front/issues/613), [T04 #614](https://github.com/DevKor-github/OnTime-front/issues/614):** 전체 main/DI 여정, 실제 장애·암호문, 독립 기대값·이벤트 연결. 같은 결과를 공유하되 증거 범위를 확대하지 않는다. +- **R02/R04/R01/R09:** native CI, 동일 소스 배포, 검사 강제, 최종 artifact. 별도 실제 이슈·구현 상태를 read-back해 연결한다. + +순서: 현재 소스·환경·artifact manifest 확정 → 기존 native contract 실제 실행 → 좁은 누락 contract 추가 → 실제 emulator/simulator service 연결 → T01 bridge/공통 증거 schema 연결 → 원 제품 결함 수정 결과 반영 → 지정 물리 RC 행렬 → CI/원격 artifact read-back 및 필수 행 완전성 확인. 원 A/D 이슈의 기기 AC는 삭제하지 않고 동일 실행 결과를 양쪽에 연결한다. + +## 완료 기준 + +- [ ] 기존 iOS malformed export XCTest를 실제 실행했고 Android instrumented 계약과 함께 재현 명령·xcresult/JUnit 원본이 있다. +- [ ] 실제 앱 native 등록, picker lifecycle/callback, receiver, 권한/channel, observation/cancellation 경계가 실행됐고 오류/미지원·unknown을 성공으로 바꾸지 않는다. +- [ ] Android API 경계와 iOS deployment/pre26/26+ 대표 행렬에 build-only·runtime·물리 결과가 구분돼 있다. 최신 단일 기기 결과를 구버전 호환성으로 표시하지 않는다. +- [ ] 양 OS 실제 번들 SQLCipher/background opener와 secure storage, 합성 역사 v1→v2/재열기/wrong key/plain SQLite 거부 및 key 불가 보존 증거가 있다. +- [ ] 지정 물리 Android의 권한·잠금·일반 종료·reboot·동일 서명 update·수신/탭과 물리 iOS fallback/AlarmKit 핵심 행이 실제 관측됐다. +- [ ] 로컬 picker 저장·재선택/인증·취소·중복·수명 전이, Android↔iOS 각 복원, reset 재시작의 원 기능 필수 증거를 연결했다. 주입 장애와 실제 OS 장애를 구별한다. +- [ ] 모든 결과에 실제 설치 artifact/source/merge/signing·OS/provider/권한·expected/actual·injection/evidence가 있고 필수 missing/skip을 자동 pass로 집계하지 않는다. +- [ ] 기존 사용자/QA 데이터와 키를 보존하고 합성 fixture 외 민감 자료가 artifact에 없으며 제품에 QA용 권한·full-screen·network 정책 우회가 없다. +- [ ] R02/R04 CI 연결 및 원격 결과를 확인했고 필수 물리/hardware/signing 미충족 행이 없다. 남았다면 OPEN을 유지한다. + +제외: 지원 OS/SDK 정책 임의 변경, 모든 OEM 무한 행렬, force-stop 전달·정확 시각의 무조건 보장, 실제 사용자 설치 초기화, cloud test telemetry·서버·로그인, 내부 키/DB dump debug API, store 제출·병합 성공 주장, typecheck만으로 native 실행 완료 선언. + +## grill-with-docs 실제 전체 문답 + +아래 질문/답변/조사 보충은 실제 전담 agent와 root의 교환을 보존한다. root가 사용자를 대신해 결정했다. 의도한 답을 소급해 만든 합성 문답이 아니다. + +### Q1 — 소유 범위와 증거층 + +**Agent:** +> T02 grill-with-docs를 읽고 고정 7a6825f79a565c9b006f3ec57b837271b0e06ba4 조사했습니다. A02 RunnerTests malformed export는 실제 소스가 있으나 기록은 typecheck까지이고, Android release APK/packaged manifest CI도 이미 성공했습니다. Q1 — T02 완료 범위와 증거층을 권고대로 확정할까요? 권고: 기존 소스/manifest/typecheck, 실행된 native contract(XCTest·Android instrumented), emulator/simulator의 실제 플랫폼 서비스 연결, 명시 지정 물리 release candidate의 OS 행동을 각각 구분합니다. T02는 기존 RunnerTests의 실제 실행과 picker lifecycle/receiver/channel/native observation 계약 확장, 번들 SQLCipher·Keychain 계약 실행 및 권한/provider/잠금/재부팅·업데이트 물리 행렬을 소유하고, T01 전체 앱 정상 여정·T03 장애 주입·T04 독립 oracle을 재사용합니다. 원 기능 A01–A15/D01–D05의 실기기 AC를 T02로 옮겨 삭제하지 않고 동일 증거로 양쪽 연결합니다. compile/registry/channel 성공은 실제 전달·picker 완료·암호화 실행 PASS가 아니며 hardware/signing 없으면 해당 행과 이슈 OPEN을 유지합니다. Android full-screen 승인 false는 그대로 두고 T02를 위해 정책을 켜지 않습니다. 새 도메인 용어/ADR는 불필요합니다. + +**Root:** +> Q1 권고 채택. 기존 source/manifest/typecheck→실행nativecontract→emulator/simulator 실제platform연결→지정물리RC OS행동을 분리. RunnerTests 실제실행과 Android instrumented 계약, picker lifecycle/receiver/channel/observation 및 실제 SQLCipher/Keychain 핵심 경계를 T02가 연결. T01 전체정상여정/T03장애/T04oracle 재사용, 원A/D AC는 삭제하지 않고 동일실행증거crosslink. compilation/channel/registry=전달완료·파일완료·cipher동작으로 과장금지. Android full-screen 정책승인 false유지, QA편의 권한/정책활성화금지. physical/signing미확보행OPEN. Keychain/cipher관측은 합성fixture·키비노출, productionDB/plaintextdump를 증거에 내보내지 않음. 새glossary/ADR불필요. + +### Q2 — 유한 OS 행렬과 기기 보존 + +**Agent:** +> T02 Q2 — OS 조합을 무한 곱집합 대신 위험 경계별 최소 필수 행렬로 고정할까요? 권고: Android 자동 계약은 실제 선언 minSdk 23 계열, 알림 채널 API 26, exact access API 31/32, POST_NOTIFICATIONS API 33+, 실제 release target/현재 지원 최신을 경계별 대표로 기록(같은 기기가 경계를 함께 입증하면 중복 생략); 물리 RC는 현재 지원 Android 대표 1대 이상에서 notification master/channel 거절·허용, timing exact 거절/허용/회수, foreground/background/일반 process 종료/잠금/재부팅/동일 서명 update, 실제 수신·탭·비공개 문구를 필수로 둡니다. iOS는 deployment 15의 build/native fallback 호환성, iOS 26 미만 fallback 대표 runtime, AlarmKit 가용 26+ runtime을 나누고 물리 RC에서 fallback 및 AlarmKit 허용/거절·권한변경·잠금·재실행·update를 각각 입증합니다. 한 최신 iPhone에서 AlarmKit 거절로 fallback을 봤다고 pre-26 호환성까지 통과시키지 않습니다. OEM QUICKBOOT/추가 OEM·Focus/배터리 제한 등 변형은 이름·가용성·관측 결과를 명시한 조건부 확장이고 소유하지 않은 기기 조합은 not_run; force-stop은 별도 제한 관찰로 두며 전달 보장으로 만들지 않습니다. baseline 핵심 행은 hardware 부재로 면제하지 않되 추가 OEM 전체를 필수 무한 목표로 만들지 않는 안입니다. 재부팅 전 최초 잠금해제 이전 key 접근은 '잠금으로 읽기불가'와 key없음 구별/데이터·key 재발급 금지를 관측하되 실제 reboot/lock 기기 없으면 pending입니다. 정확한 대표 OS patch/모델은 실행 manifest에 확정하고 최신 문서라는 이유로 toolchain/지원 범위를 바꾸지 않습니다. + +**Root:** +> Q2 채택. 현재 repo가 선언한 지원 하한·API변화 지점·release target을 기준으로 대표경계 행렬, 동일실행이 여러행을입증하면중복실행없음. Android min23/channel26/exact31~32/POST33+/현재target 대표는 실제 설치·실행가능성과 build만증거를구분, 지원하한을맞추려 도구버전임의변경금지. 물리 Android1대이상 핵심권한변경/잠금/일반kill/reboot/동일서명update/수신탭/비공개문구 필수. iOS15 build+fallback 호환성, pre26runtime,26+AlarmKit runtime 구별하고 물리fallback·AlarmKit 행 명시. 최신폰 fallback만으로pre26pass불가. OEM QUICKBOOT/Focus/배터리추가는유한조건부행,없는기기는not_run; force-stop특수제약과OS정책기록,전달보장없음. 최초unlock전 secure store unavailable≠missing,키/DB재생성금지증거필수. 정확모델/OSpatch/toolchain을manifest고정. 자동기기초기화/권한변경은자기소유disposable만,물리QA는명시지정된대상에서안전한합성데이터만. 새OS가출시됐다는사유로이번지원정책/SDK업그레이드로범위확장하지않음. + +### Q3 — 저장소·picker·CI·종결 + +**Agent:** +> T02 Q3 — storage/picker의 독립 판정과 CI 종결 기준을 확정하겠습니다. 권고: 양 OS native picker 정상 저장→같은 파일 재선택/완전 인증, 취소·중복·background·activity 재생성/scene disconnect·늦은 callback·실제 provider 쓰기/flush/close 오류·임시파일 정리 행을 나누고 callback 한 번/실제 파일/freshness/current generation을 함께 판정합니다. provider 오류를 fixture adapter로 주입한 행은 실제 OS provider 고장과 구분합니다. 로컬 provider가 기본, cloud sync 완료는 주장하지 않으며 Android→iOS와 역방향 각 1건 전체 durable 동등성·runtime 제외는 A02/T01의 동일 증거를 연결합니다. SQLCipher는 제품 background opener 및 실제 번들 라이브러리에서 cipher_version, 합성 역사 v1→v2 재열기, wrong-key·plain SQLite 읽기 거부를 확인하고 키/생산 DB를 출력하지 않습니다. Keychain/secure storage는 재실행 보존·잠금 접근불가·missing/corrupt/read-failure를 분리, D01 Recovery 및 D03 reset 재시작에서 old key/data/OS ownership 잔존 판정을 연결하며 uninstall=키제거로 추정하지 않습니다. T02는 재현 native 실행 entrypoint/결과 schema/필수 행과 source+merge SHA·installed artifact digest·서명종류·OS/model/provider·expected/actual·injection/evidence를 만들고 R02/R04가 CI/build 배선, R01필수검사, R09release네트워크를 소유합니다. native 실제 실행은 xcresult/JUnit 등 원본과 실패 로그를 보존하고 필수행 missing/skip을 성공집계 금지. watchdog이 지나도 실제 아직 반환 안 된 OS작업의 owner를 풀지 않으며 delayed 결과는 test에 기록합니다. 최소 강제 green 없이 필수 자동native+양physical 핵심행 모두 증거 충족 시 완료, 환경 blocked면 OPEN인 안입니다. + +**Agent 조사 보충:** +> Q3 조사 보충: iOS export는 앱 own tmp의 write/sync/readback 뒤 export-as-copy delegate를 받아 saved를 판정하므로 외부 provider의 내부 flush/close를 앱이 관측한다고 쓰지 않겠습니다. Android ContentResolver stream 오류와 iOS staging/presentation/delegate 오류를 각 native 소유 경계로 분리하고, 최종 로컬 파일 재선택·인증은 공통 end-to-end oracle로 둡니다. 공식 Flutter 문서는 integration_test가 permission/notification 같은 native UI를 조작하지 못한다고 명시하여 native driver/수동 기기 행 분리 근거로 사용했습니다. Apple 문서 URL은 확인했지만 본문 markdown이 도구에서 로드되지 않아 API 세부 주장은 repo 코드/SDK 및 실행 시 공식 문서 재확인으로 제한합니다. + +**Root:** +> Q3 채택. picker 단계별 callback·실제 파일 인증/flush close·freshness·generation을 함께관측하고 adapter fault와 OS실제오류 구별. Android↔iOS각1건 durable동등/runtime제외 기존A02/T01실행증거재사용, localprovider기본 cloud완료주장없음. 실제background opener/번들 cipher_version·역사v1→v2·wrongkey/plainSQLite거부를 synthetic DB로,키/원문dump금지. securestore missing/corrupt/readfailure/locked구별,잠금은새키생성아님;reset재시작D03ownership같이기록. native entrypoints/manifest/xcresult/JUnit/원실패보존,필수행missing/skip=미완료;toolwatchdog과실제OSowner수명구별. 서명종류/설치artifactdigest/source+merge/OSmodelprovider/injection/expectedactual기록. R01/02/04/09책임분리유지,필수자동native+양물리핵심행완료전OPEN. 스토리지 보안의 cipher_version/wrong-key거부는강한실행증거지만전체암호학적보안감사를완료했다는과장금지. 실제문답전체·정정baseline·검증행렬·근거·의존성으로T02문서동결해주세요. + +**Root 보충:** +> Q3 보충수령·채택. iOS 앱소유tmp write/sync/readback + export-as-copy delegate만앱관측, 외부provider내부flush/close완료직접관측주장금지. AndroidContentResolver와iOSstaging/presentation/delegate경계각기명시, 최종파일재선택·전체인증공통oracle. 로드못한공식본문을확인된근거처럼인용하지않고읽은Flutter공식범위/실제repoSDK확인구별. 이보충문답도누락없이포함해주세요. + +위 Q2 질문의 “재부팅 전 최초 잠금해제 이전” 표현은 문맥상 **재부팅 후 최초 unlock 이전** 경계를 가리키며 root 답변과 본문 P11을 그 의미로 적용한다. 질문 원문은 수정하지 않고 보존했다. 추가 제품 정책이나 새 용어 결정은 없으며 상세 이슈 게시·구현·검증은 root가 이어간다. + +### 게시 후 root 검토 — Schedule 시작 예약과 A06 단계 알림 구별 + +**Root:** +> root 게시 직후 P03 행에서 '일반 process 종료 후 실제 미래 전달'에 A06 링크가 같이 있어 preparation step 알림까지 kill중보장처럼 읽힐 위험을 확인했습니다. A06 최종계약은 연속실행paused periodic만, suspend/kill정시단계알림보장없음. T02 P03을 '예정된 schedule-start OS예약의 foreground/background/일반종료 전달'과 'A06 active periodic은 실행가능paused 관측시에만, resume/catch-up무재생'으로 명확히 구분해주세요. 신규정책아닌확정A06보존, 실제 이 root리뷰 원문 및 답변도 문서말미추가. P04상세privacy링크A05#588도추가. 본인T02.md만 수정후새hash통보, root원격본문갱신할것. 제품/다른문서작업금지. + +**Agent:** +> 리뷰 수용합니다. P03은 예정된 Schedule 시작 OS 예약의 전달로 한정하고, A06 active periodic 단계 알림은 별도 행에서 연속 실행 가능한 paused 관측만 허용하며 suspend/kill 정시 보장·resume/catch-up 재생이 없다고 명시하겠습니다. P04에 A05 #588을 연결하고 이 리뷰 원문과 현재 답변을 그대로 문서 말미에 추가하겠습니다. 기존 확정 계약의 표현 정정이며 새 정책 결정은 아닙니다. diff --git a/plans/audit-2026-09-23/issues/U08.md b/plans/audit-2026-09-23/issues/U08.md index a64a6e6b..67bfa7da 100644 --- a/plans/audit-2026-09-23/issues/U08.md +++ b/plans/audit-2026-09-23/issues/U08.md @@ -146,3 +146,9 @@ Android와 iOS 각각 최소 한 번 실제 OS 큰 글자 설정에서 핵심 - `plans/audit-2026-09-23/artifacts/u08-home/u08-home-430-empty.png`, `plans/audit-2026-09-23/artifacts/u08-home/u08-home-430-schedule.png`를 실제 번들 폰트·banner로 캡처하고 시각 확인했다. 이 PNG는 **독립 HomeScreenContent를 MaterialApp/Scaffold 안에 렌더링한 widget 증거**이며 production-router screenshot, 전체 앱 chrome/safe-area, 물리 기기 증거가 아니다. 430×932 EN, DPR 1, 글자 1.0배, safe-area 0이다. production router 두 viewport의 검증은 위의 별도 실행 결과다. - 소스 SHA256·명령·로그·이미지 SHA256은 `plans/audit-2026-09-23/u08-home-validation.json`에 기록했다. 공유 worktree에서 A13 작업이 병행되었음을 명시했다. 전체 테스트/CI 통합은 root 후속 작업이며 U08 담당자는 targeted 검증만 수행했다. - **전체 U08는 미완료:** 4 viewport×2언어×3배율×safe-area의 4화면 매트릭스, 전체 이름 접근, Android/iOS OS 큰 글자 검증은 아직 남아 있다. 이번 선행 홈 패치로 이슈를 닫지 않는다. + +## A07 통합 중 추가 확대 글자 보완 + +A07 커밋 `d73c7863a01343004ccadcd72064b563c464dd0d`의 실제 오류/partial 화면 200% 검증에서 graph 고정 높이72px 및 preparation tile22px overflow, 시작 버튼 레이블 잘림을 확인했다. graph/tile/skip/start 버튼을 내용 기반 최소 높이로 바꿔 KO/EN 430×932·200% 화면 캡처4개 및 실제 목록 스크롤·skip tap·다음 단계 hit-test를 통과했다. 원본 실패와 수정 후 로그·PNG는 `plans/audit-2026-09-23/artifacts/a07/`에 있고 전체944 tests/analyze·coverage88.67%도 통과했다. + +이 검증은 합성 receipt를 렌더한 widget 증거이며 전체 U08 화면/기기 행렬 완료가 아니다. U08은 OPEN을 유지한다. 영어 화면의 기존 한국어 시간·skip 문구는 U05에 남는다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 584c0a11..d400f8fb 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -428,18 +428,27 @@ "`lib/domain/use-cases/schedule_preparation_session_use_case.dart:84-87`", "실패 시 재시도 가능, 성공 중복 호출은 멱등. 첫 write 실패→두 번째 성공과 동시 호출을 실제 repository 경계에서 검사." ], - "status": "ready", + "status": "code_verified_ci_device_pending", "agent": "/root/grill_a07", "issue_number": 599, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/599", "decisions_file": "plans/audit-2026-09-23/issues/A07.md", - "implementation_commit": null, - "pr_url": null, - "verification": [], - "blockers": [], + "implementation_commit": "d73c7863a01343004ccadcd72064b563c464dd0d", + "pr_url": "https://github.com/DevKor-github/OnTime-front/pull/585", + "verification": [ + "Actual Drift pre-fix three failure cases confirmed; focused56 passed", + "Full944 tests/analyze clean; coverage88.67% (13289/14987); Python11/manifest/generated/local-only pass", + "29 source/test/QA file freeze and committed blobs independently hash-verified", + "KO/EN 200% error/partial screenshots; list scroll/skip/next-step hit test pass; synthetic receipt screenshots distinct from real SQLite button regression" + ], + "blockers": [ + "Remote CI for this implementation pending", + "Physical Android/iOS lifecycle and delivery acceptance pending" + ], "sequence": 13, "readback_verified": true, - "body_sha256": "6d58178bbc4ffcfe52875c46537bb8eb3250cd4f9a1f13356bb7a44475065b20" + "body_sha256": "6d58178bbc4ffcfe52875c46537bb8eb3250cd4f9a1f13356bb7a44475065b20", + "issue_body_sha256": "3a5af2d11207eb219ce324b55367fcf5270204bac6c59df67f2fdbe7c1d7d7c7" }, { "audit_id": "C01", @@ -803,16 +812,18 @@ "네이티브 계약 테스트와 실기기 매트릭스를 분리한다.", "method channel 성공 mock에 그치지 않고 receiver/merged manifest/file picker/keychain/SQLCipher/AlarmKit·fallback을 검사. OS 권한·잠금 화면은 native UI 자동화 또는 수동 기기 절차로 검증." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_t02", + "issue_number": 616, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/616", "decisions_file": "plans/audit-2026-09-23/issues/T02.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 29 + "sequence": 29, + "readback_verified": true, + "issue_body_sha256": "e44eecda9ac22ab41a5068261b0e86acdf7325081cdb4fa0ef3cd4ad467ab31b" }, { "audit_id": "R02", @@ -1087,14 +1098,16 @@ "pr_url": null, "verification": [ "Home prerequisite patch: actual-font430x932 14px overflow red->green; 18 focused tests (home14/router4) and targeted analyzer passed.", - "Root visually inspected scheduled-home PNG; widget render only. u08-home-validation.json source/image/log hashes matched." + "Root visually inspected scheduled-home PNG; widget render only. u08-home-validation.json source/image/log hashes matched.", + "A07 d73c7863 scoped graph/tile/start-button 200% overflow fix; KO/EN capture/scroll/skip hit tests pass; full944/analyze 88.67%; full U08 matrix remains pending" ], "blockers": [ "Remaining full U08 viewport/locale/scale/safe-area matrix and Android/iOS OS large-font validation." ], "sequence": 42, "readback_verified": true, - "body_sha256": "ff5cd4c85fbe77fe189f19b2ab322c8d54da3c1f2f9f087362316b2fb4de4705" + "body_sha256": "ff5cd4c85fbe77fe189f19b2ab322c8d54da3c1f2f9f087362316b2fb4de4705", + "issue_body_sha256": "256176cc0df5447b4b93ff0f7bf8cfa2483fe014c3a8a9862fac6366192cb119" }, { "audit_id": "U09", From 3771615dd2b41089330ba74277d85451d4421c4f Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:41:21 +0900 Subject: [PATCH 44/59] docs: define required mobile PR validation contract --- plans/audit-2026-09-23/README.md | 4 +- plans/audit-2026-09-23/issues/R02.md | 170 +++++++++++++++++++++++++++ plans/audit-2026-09-23/state.json | 12 +- 3 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/R02.md diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 8da04cec..d82b7668 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -48,7 +48,9 @@ | T02 | [#616](https://github.com/DevKor-github/OnTime-front/issues/616) | 실제3문답·native15/플랫폼물리20행렬·OS경계/기기격리·실행증거 계약 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **30/64개**를 게시하고 원격 본문 동일성을 확인했으며 **34개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200%를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +| R02 | [#617](https://github.com/DevKor-github/OnTime-front/issues/617) | 실제3문답·동일 PR 소스 양모바일 build/native/smoke·최소권한/실패판정·12행 CI 검증 | + +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **31/64개**를 게시하고 원격 본문 동일성을 확인했으며 **33개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200%를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/R02.md b/plans/audit-2026-09-23/issues/R02.md new file mode 100644 index 00000000..be8f9cd6 --- /dev/null +++ b/plans/audit-2026-09-23/issues/R02.md @@ -0,0 +1,170 @@ +# [P1][R02] 일반 PR에 Android·iOS 빌드와 실제 native·앱 smoke CI를 연결하고 누락 검사를 실패로 판정 + +## 문제와 결과 목표 + +일반 PR에서 Dart 분석·테스트가 통과하더라도 Swift/Kotlin, native plugin 등록, 기기용 iOS 구성, OS 서비스 연결이 깨질 수 있다. Android release APK의 manifest 검사는 이미 추가됐지만 iOS 빌드와 실제 native test 실행, 두 모바일 플랫폼의 실제 앱 smoke는 현재 PR workflow에 연결돼 있지 않다. + +R02는 **동일 PR 검증 소스에서 Android·iOS 빌드, 실제 native tests, 결정적인 앱 smoke를 실행하고, 실행하지 않은 필수 검사나 사라진 결과를 성공으로 표시하지 않는 CI**를 만든다. T01/T02의 테스트 entrypoint와 증거 schema를 배선하며 별도 테스트 제품이나 중복 suite를 만들지 않는다. + +- 우선순위: **P1**, 감사 R02. 전담 grill agent: `/root/grill_r02`. +- 적용 skill: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +- 고정 조사 source: **`d73c7863a01343004ccadcd72064b563c464dd0d`**. 병행 C01 WIP와 이후 HEAD의 실행 결과는 아래 고정 소스 증거가 아니다. +- 실제 root 문답 3개로 결정했다. 이 문서 작성은 읽기·공식 문서 조사와 이 파일 작성만 수행했으며 workflow·제품·테스트·GitHub 설정을 변경하거나 Flutter/native test를 실행하지 않았다. +- **감사 baseline 정정:** “일반 PR에는 Android build가 없다”는 현재 사실이 아니다. A01 이후 Android Notification Contract가 PR에서 release APK를 빌드하고 packaged/merged manifest를 검증한다. “iOS RunnerTests가 placeholder”도 현재 사실이 아니다. A02 malformed export 입력 계약 테스트가 있으며 다만 실제 실행을 PR에서 강제하지 않는다. + +## 고정 코드와 기존 증거 + +| 고정 근거 | 현재 사실·남은 간격 | +|---|---| +| [Flutter Testing](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/.github/workflows/flutter_test.yml) | main/release/**/hotfix/** PR·push 및 dispatch, Ubuntu, Flutter 3.44.4, native SQLite/sodium 설치, generator, generated/local-only policy, analyzer, full unit/widget 및 coverage 80%가 있다. native 모바일 실행과 iOS build는 없다. | +| [Android Notification Contract](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/.github/workflows/android-notification-contract.yml) | Java17, Flutter3.44.4, 실제 release APK, source negative fixtures, packaged/merged manifest 검사, APK digest 및 30일 artifact가 있다. contents:read, concurrency, 35분 timeout도 이미 있다. 일회용 검증 서명이며 `device_delivery_verified: false`다. | +| [저장된 A06 CI 검증](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/plans/audit-2026-09-23/a06-ci-validation.json) | source e6c1438e의 Flutter 929/88.79% 및 Android APK 성공이 기록돼 있다. 실제 빌드 merge 0896d865…와 source head가 다름을 명시한다. 이 결과는 d73c7863의 새 native CI 실행 결과가 아니다. | +| [Android build.gradle](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/android/app/build.gradle) | compileSdk36, minSdk23, Java17, Flutter의 targetSdkVersion, release signing 필수 검증. PR용 disposable key를 재사용하고 production 키를 요구하지 않는다. | +| [Android settings](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/android/settings.gradle) / [Gradle wrapper](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/android/gradle/wrapper/gradle-wrapper.properties) | AGP8.6.0/Kotlin2.2.0/Gradle8.9를 프로젝트에서 선언한다. runner의 전역 Gradle/Java 최신값으로 바꾸지 않는다. 고정 tree에는 android/app/src/androidTest 및 src/test의 native suite가 없다. | +| [RunnerTests](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/ios/RunnerTests/RunnerTests.swift) / [Runner scheme](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme) | malformed export 인자에 단일 실패 응답을 검사하는 XCTest와 TestAction의 RunnerTests 항목이 존재한다. typecheck나 build-for-testing은 test 실행 결과가 아니다. | +| [iOS project](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/ios/Runner.xcodeproj/project.pbxproj) / [Podfile](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/ios/Podfile) | deployment target15.0, Swift5, Runner/RunnerTests와 CocoaPods가 있다. 기존 team ID가 있다고 signing credential이 CI에서 가용하다고 가정하지 않는다. | +| [AppDelegate](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/ios/Runner/AppDelegate.swift#L140) | `canImport(AlarmKit)`와 iOS26 availability로 native 경로를 분기한다. SDK가 너무 오래돼 해당 코드를 빼고 컴파일한 결과를 AlarmKit 경로 컴파일 증거로 삼지 않는다. | +| [pubspec](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/pubspec.yaml) / 고정 tree | integration_test 의존성과 integration_test/ 실제 앱 entrypoint가 없다. 추가는 T01이 소유한다. 생성 Dart는 ignored local artifact 정책을 유지한다. | +| [Android internal deploy](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/.github/workflows/android-play-internal.yml) | 수동 main/staging 배포가 production signing·Play credential을 사용한다. 일반 PR 검사에 이를 호출하지 않는다. 동일 품질/배포 결합은 R04, track 경쟁/환경 보호는 R05다. | + +Android·iOS만 지원하는 [ADR0016](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/docs/adr/0016-support-local-only-on-android-and-ios.md), release network boundary의 [ADR0024](https://github.com/DevKor-github/OnTime-front/blob/d73c7863a01343004ccadcd72064b563c464dd0d/docs/adr/0024-enforce-no-network-in-product-builds.md), CONTEXT의 Local Profile·Offline Cold Start·Schedule Notification 계약을 보존한다. runner/job/check는 구현 용어이므로 glossary/ADR를 새로 만들지 않는다. + +## 확정한 CI 실행 범위 + +| 검사 | 실행·판정 | 책임·한계 | +|---|---|---| +| 기존 Flutter quality | clean checkout에서 lockfile 기반 설치, generator, generated/local-only policy, analyze, full tests, coverage80 | 기존 Flutter Testing 재사용. threshold 완화·generated commit·npm test 대체 금지. | +| Android release validation | 기존 release APK + packaged/merged manifest + digest | Android Notification Contract 재사용. 임시 키는 store 키가 아니며 OS 수신 증거가 아님. | +| iOS release device-target build | iOS26 SDK를 지원하는 명시 Xcode로 unsigned Release 기기 아키텍처 구성 전체 빌드 | simulator build만으로 대체하지 않음. unsigned app은 배포 가능한 signed IPA나 물리 설치 검증이 아님. | +| iOS native tests | 새 소유 simulator에서 기존 RunnerTests 및 T02 확장 XCTest 실제 실행, xcresult와 예상 case 확인 | build-for-testing/typecheck 성공, test0개, placeholder는 통과가 아님. | +| Android native tests | T02의 instrumented/native entrypoint를 소유 emulator에서 실행, JUnit·예상 case·assertion 확인 | 컴파일 task만 실행하거나 native suite가 없는 상태의 no-op Gradle 성공은 통과가 아님. | +| Android·iOS core app smoke | T01 entrypoint를 실제 main/startup/DI/native 저장소·UI 경로에 연결, 각 플랫폼 결정적 core 시나리오 실행 | native UI는 T01/T02 driver 사용. callback 주입 또는 widget-only 테스트로 강등하지 않음. | +| mobile aggregate | 위 새 mobile workflow의 모든 필수 job/result를 검사하는 안정적인 check | 기존 별도 Flutter/Android workflow의 성공까지 추정하지 않음. R01이 별도 check들을 함께 요구. | + +T01/T02가 아직 제공하지 않은 suite/driver는 **dependency pending**이다. R02는 이미 실행 가능한 빌드와 XCTest부터 구현할 수 있지만 필수 Android native 또는 양 플랫폼 core smoke를 비운 채 완료하지 않는다. 구현 단계마다 실제 배선된 행·미배선 행을 기록하며 no-op 성공 check로 자리를 채우지 않는다. + +### 유한 runtime 행렬과 toolchain + +- Flutter **3.44.4**, Dart **3.12.2**, Java17 및 저장소가 잠근 Gradle/Kotlin/plugin/Pods를 사용한다. 지원 하한 Android23/iOS15 또는 제품 local-only 정책을 CI 편의를 위해 바꾸지 않는다. +- PR은 Android/iOS 각각 대표 runtime을 명시한 유한 행렬이다. Android는 실제 target/SDK와 설치 가능한 대표 image를 확인하고 API·ABI·image revision을 기록한다. iOS는 명시 Xcode 경로·SDK·runtime·device type을 검증한다. `latest`, 연결된 첫 기기, 암묵적인 기본 simulator에 의존하지 않는다. +- 조사 시 공식 [macOS26 runner 이미지 목록](https://github.com/actions/runner-images/blob/main/images/macos/macos-26-Readme.md)에는 Xcode26.5/26.6, iOS26.5 SDK/runtime가 나온다. 이는 후보 선정 근거이며 이 저장소의 runner 할당·실제 부팅·호환 성공 증거는 아니다. 구현 시 제공 목록과 원격 runner의 `xcodebuild -version`, SDK 목록, `simctl` runtime 목록을 다시 확인하고 정확 버전을 고정한다. image가 바뀌어 필수 도구가 사라지면 조용히 다른 버전으로 fallback하지 않고 환경 실패를 남긴다. +- T02의 Android23/26/31~32/33+/target, iOS15 호환 build/pre26 fallback runtime/26+ AlarmKit runtime 및 물리 RC 행은 별도 명시 확장 실행으로 연결한다. 최신 iOS에서 fallback이 보였다고 pre26 호환성을 통과시키지 않는다. R02 대표 PR 행이 T02 전체 물리 행렬을 면제하지 않는다. +- runner가 만든 새 disposable AVD/simulator ID·run ID만 설치·permission 변경·종료·삭제 대상으로 삼는다. 기존 QA simulator `C8BA159D-A97A-458B-A5FC-B04A8B6F2848`, 연결된 사용자 기기와 다른 작업 데이터는 보존한다. + +## Trigger·권한·필수 검사 계약 + +1. `pull_request`의 main/release/**/hotfix/** 대상과 해당 branch `push`, `workflow_dispatch`를 지원한다. `merge_group: checks_requested`도 같은 검사 경로를 지원하되 merge queue의 활성화/보호 설정은 R01에서 다룬다. +2. 모든 일반 PR에서 고유하고 안정적인 check 이름을 유지한다. 문서-only 변경이나 draft를 이유로 필수 workflow/job 전체를 skip하지 않는다. required check에 영향을 주는 rename은 R01 목록과 같이 검토한다. GitHub는 workflow filter로 빠진 required check가 pending을 유지할 수 있다고 설명하므로 필수 workflow에 path filter를 두지 않는다. [공식 workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax) +3. 최소 `contents: read`, checkout `persist-credentials: false`. production signing, Play/App Store, OIDC, 보호 environment secret을 PR에 제공하지 않는다. Android 일회용 key/검증용 고정 password는 production credential이 아니며 실행 종료 시 제거한다. iOS는 unsigned/device build와 simulator test로 검사한다. +4. fork PR도 같은 테스트 코드 경로를 사용한다. GitHub의 fork 승인 대기는 미실행이며 pass가 아니다. untrusted head를 `pull_request_target` 또는 높은 권한의 `workflow_run`으로 우회 실행하지 않는다. 이 구분은 GitHub의 [pull_request_target 보안 문서](https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target)에 근거한다. +5. aggregate는 `always()` 조건으로 실제 필수 job의 결과를 검사한다. `success` 외에 `cancelled`, `skipped`, 누락/0 tests, 필수 report 부재, dependency pending을 통과로 취급하지 않는다. 테스트 runner가 반환한 0 exit code 외에 예상 suite/case ID와 결과 원본을 확인한다. 전체 run 취소 등으로 aggregate 자체가 실행되지 못했으면 그 run 역시 성공 증거가 없다. +6. mobile aggregate는 자신의 `needs`와 artifact를 검증한다. 별도 workflow의 Flutter quality와 Android Notification Contract는 R01 필수 check 목록에 각각 포함한다. cross-workflow 결과를 실제 조회·검증하지 않은 aggregate가 전체 repository quality를 보장한다고 말하지 않는다. merge queue 검사 트리거 필요성은 [GitHub required checks 문서](https://docs.github.com/en/pull-requests/how-tos/merge-and-close-pull-requests/troubleshooting-required-status-checks)를 따른다. + +### 시간·동시 실행·실패 보존 + +- concurrency key는 workflow + PR 또는 검증 ref를 포함한다. 오래된 PR/merge 검증 실행만 새 실행으로 취소한다. 수동/main/release 검증은 취소하지 않는 것을 기본으로 명시하고 배포 job과 같은 그룹에 묶지 않는다. group이 다른 PR이나 다른 workflow까지 잘못 취소하지 않는지 fixture로 검증한다. +- matrix `fail-fast: false`로 반대 플랫폼의 진단을 보존한다. 초깃값 목표는 기존 Android release35분, Android native45분, iOS build/native60분, 각 smoke45분이다. 전체 job과 boot/test의 watchdog을 모두 유한하게 두며 실제 실행 시간으로 조정 근거를 남긴다. 같은 build를 job마다 무조건 반복하지 않고 같은 소스·mode·toolchain으로 재사용 가능한 산출물만 공유한다. +- `continue-on-error`, 성공할 때까지 자동 재시도, 실패 case 삭제로 green을 만들지 않는다. 수동 재실행은 원 실패 run/attempt와 후속 결과를 모두 연결한다. +- 실패 진단 업로드와 소유 target cleanup은 가능한 범위에서 always 수행한다. runner 강제 종료가 모든 cleanup hook 성공을 보장한다고 주장하지 않는다. 테스트 watchdog이 앱의 아직 반환하지 않은 OS 작업 owner를 임의 해제하는 근거가 되지 않는다. +- PR 작업 취소/시간 제한은 R02의 새 검사 기본값에 포함한다. Play package/track 직렬화, staging 환경 보호, 배포 작업의 중단 의미는 R05로 남긴다. + +## Artifact·재현성·동일 소스 판정 + +각 job은 기계 판독 manifest와 native/Flutter 원본 결과를 연결한다. 최소 필드는 아래와 같다. + +| 범주 | 필수 내용 | +|---|---| +| 소스 | PR head SHA, base SHA, 실제 checkout/merge SHA; push·merge_group이면 해당 실제 검증 SHA. `git rev-parse HEAD`와 event를 같이 기록. | +| 실행 | workflow/run ID·run attempt·job/check 이름, event, 시작/종료, command, exit/result, runner image/OS/architecture. | +| 도구·입력 | Flutter/Dart, Java/Gradle/AGP/Kotlin, Xcode/SDK/runtime, lockfile hashes, native driver version, fixture/run ID. | +| 앱·대상 | build mode/version/build number, 실제 app ID, target ID/OS/ABI, signing 종류, 실제 빌드·설치 artifact digest. | +| 검사 | 예상 case IDs와 실행/pass/fail/blocked/not_run/skipped 수, 독립 assertion 위치, injection/driver 경계, 결과 원본·log·screenshot hash. | +| 한계 | compile-only/native execution/emulator/simulator/physical 구분, unsigned/disposable signing, offline/network/device delivery 미검증 행. | + +- PR head와 기본 checkout merge SHA를 하나의 `commit` 필드로 뭉개지 않는다. Android 기존 identity를 확장할 때도 이전 기록의 의미를 보존한다. 서로 다른 checkout의 build/test 결과를 같은 검증 세트로 결합하지 않는다. +- Android debug/instrumented APK, Android release validation APK, iOS simulator app/test bundle, unsigned device Release app는 서로 다른 산출물이다. 설치한 binary의 digest를 기록하고 다른 release binary에 그 결과를 붙이지 않는다. app bundle directory는 재현 가능한 파일 manifest/hash 방법을 명시한다. +- 합성 fixture의 JUnit/xcresult·테스트 manifest·필요 로그·실패 화면을 성공/실패 모두30일 보존한다. binary는 재현에 필요한 검증용 산출물만 선택적으로 저장한다. production 키·암호·사용자 DB·실사용 설치 데이터·임시 signing key는 artifact와 cache에서 제외한다. +- 필수 result/identity가 없거나 필요한 업로드가 실패하면 테스트 assertion PASS와 별도로 CI evidence 실패이며 aggregate는 통과하지 않는다. 부팅 전에 실패해 report가 없는 경우는 명확한 환경 실패 진단을 남기되 성공으로 채우지 않는다. +- cache key는 OS/architecture/toolchain/lockfile을 구별한다. dependency cache만 재사용하며 생성 Dart·제품 build output·key/data를 신뢰 가능한 새 결과 대신 복원하지 않는다. 실행용 동일 run artifact 공유는 명시 provenance/digest 검증으로만 허용한다. cold cache와 warm cache를 각각 확인해 누락된 생성 단계나 로컬 잔여물이 성공을 숨기지 않는지 검사한다. +- CI의 dependency 다운로드는 도구 실행 환경이다. 제품에 network client/권한을 추가하거나 remote telemetry를 넣는 근거가 아니다. 제품 release network/최종 APK·IPA 관찰은 R09의 별도 계약이다. + +## 구현 순서와 검증 행렬 + +1. 기존 workflow/check 이름·source/merge 기록·실제 runner toolchain을 확정한다. T01/T02 entrypoint 상태와 필수 suite/case manifest를 읽는다. +2. 기존 Android release manifest와 Flutter quality를 유지하면서 iOS unsigned device-target build 및 RunnerTests 실제 실행을 추가한다. SDK가 AlarmKit 경로를 실제 컴파일하는지 확인한다. +3. T02 Android native 및 T01 양 플랫폼 core smoke를 같은 source에 연결한다. 테스트 의미/driver는 해당 이슈의 계약을 사용한다. 새 native framework를 R02에서 임의 도입하지 않는다. +4. 최소 권한·fork 경로·정확 triggers·bounded 실행·artifact와 aggregate를 구현한다. R01에 최종 실제 check 이름을 전달한다. +5. 아래 실패 조건을 격리된 일회성 검증 또는 gate fixture에서 확인한다. 테스트를 고장 낸 source를 일반 제품 변경으로 남기지 않는다. 각 negative는 방법/한계·원 실패·수정 후 결과를 연결한다. +6. 실제 원격 PR의 동일 merge/source에서 모든 필수 검사가 실행되고 결과 artifact를 다운로드해 manifest/hash를 다시 확인한다. 그 뒤 R04/R01/R09와 증거를 연결한다. + +| ID | 자극 | 기대 판정 | +|---|---|---| +| CI01 | 깨끗한 checkout, cold dependency cache | generator와 lockfile 설치 후 두 플랫폼 build/native/smoke 실행. 로컬 ignored output에 의존하지 않음. | +| CI02 | 같은 조건 warm cache | 같은 필수 cases 실행, cache hit를 테스트 대체로 쓰지 않음. | +| CI03 | 격리된 Swift compile error | iOS build 실패, mobile aggregate 실패, compiler 진단 보존. | +| CI04 | native assertion 실패 | 실제 native test result 실패가 job/aggregate로 전파됨. 컴파일 성공으로 덮이지 않음. | +| CI05 | suite0개 또는 expected case/report 삭제 | runner exit0이어도 aggregate 실패. 누락 case 목록 표시. | +| CI06 | 필수 child job skip/cancel/failure | aggregate success 불가. 전체 run cancel로 aggregate 미실행이어도 검증 완료 아님. | +| CI07 | T01 core smoke assertion 실패 | 양 플랫폼 중 한쪽 실패를 다른 쪽 성공으로 상쇄하지 않음. fixture/화면/로그 보존. | +| CI08 | emulator/simulator boot timeout·runtime 부재 | 유한 실패·대상/환경 진단, fallback fake나 skip-pass 없음. | +| CI09 | 필수 artifact 업로드/identity 부재·digest 불일치 | evidence/gate 실패, 잘못된 binary 결과 연결 거부. | +| CI10 | PR 새 commit으로 이전 검증 취소 | 이전/현재 attempt 분리, 최신 소스만 완료 근거. 다른 PR/배포는 취소되지 않음. | +| CI11 | fork/문서-only/draft PR trigger 조건 | secret 없이 동일 필수 검사가 생성·실행됨. 플랫폼 fork 승인 대기는 pending으로 기록. | +| CI12 | merge_group 및 branch push/dispatch | 실제 checkout SHA/trigger가 manifest에 일치. queue 활성화 여부는 별도 R01 상태. | + +CI03/04/07은 실제 runner의 compiler/test/앱 실패 전달을 입증한다. CI05/06/09의 판정기를 fixture로 검증한 경우 workflow에서 실제 해당 판정기를 호출하는 연결도 확인한다. fixture-only 검증을 원격 실패 run으로 바꿔 부르지 않는다. 비용을 이유로 같은 실패를 무한 반복하지 않는다. + +## 의존성과 완료 경계 + +- [T01 #615](https://github.com/DevKor-github/OnTime-front/issues/615): 실제 앱 smoke entrypoint/driver·fixture/expected/result. 전체 여정 및 물리 RC 성공은 T01에 남는다. +- [T02 #616](https://github.com/DevKor-github/OnTime-front/issues/616): Android instrumented·XCTest 계약·native UI bridge·OS 위험 경계 및 물리 행렬. 실제 플랫폼 test 실행을 compile-only로 대체하지 않는다. +- [T03 #613](https://github.com/DevKor-github/OnTime-front/issues/613): 빠른 장애 회귀는 기존 Flutter suite, 별도 host kill은 명시 entrypoint와 timeout으로 재사용. R02가 crypto/fault oracle을 복제하지 않는다. +- [T04 #614](https://github.com/DevKor-github/OnTime-front/issues/614): 실제 이벤트 연결/독립 기대값. fake 복제 알고리즘으로 smoke를 쉽게 통과시키지 않는다. +- R01: 실제 check 이름·최신 SHA/merge-group 검증을 ruleset으로 강제하고 원격 read-back. R02가 성공했어도 main 보호가 적용됐다고 말하지 않는다. +- R04: 같은 검증 소스·품질과 배포 artifact 연결. R02의 PR validation artifact를 store release로 승격했다고 말하지 않는다. +- R05/R06/R08/R09: 배포 경쟁·환경, 공급망 도구 pin/업데이트, release manifest·서명·store 상태, 최종 binary/network 관측. 새로운 CI 작업의 최소 안전 설정은 R02가 책임지되 이들 전체 범위를 흡수하지 않는다. + +## 완료 기준 + +- [ ] 현재 baseline 정정이 이슈와 실행 기록에 반영돼 있고 기존 Flutter/Android release-manifest 검사가 유지된다. +- [ ] 동일 실제 PR merge/source에서 iOS unsigned Release device build, 실제 XCTest, Android native tests 및 두 플랫폼 core smoke가 실행된다. +- [ ] Flutter3.44.4/Dart3.12.2와 명시 native toolchain/runtime을 확인했고 생성 Dart는 ignored artifact로 남는다. +- [ ] 일반 PR·문서-only·draft·fork·push/dispatch·merge_group 정책과 stable check 이름이 명확하며 secret 없는 fork 경로가 검증된다. 실제 플랫폼 승인 대기는 미실행으로 남긴다. +- [ ] aggregate는 필수 job 성공·expected cases·결과/identity 존재를 모두 검증하고 skipped/cancelled/0 tests/missing result를 통과시키지 않는다. +- [ ] 실패 전달·timeout·artifact 누락·source/digest 불일치의 negative 증거와 원 실패가 보존된다. broad retry/continue-on-error/threshold 완화가 없다. +- [ ] 원격 artifact를 내려받아 source/merge/toolchain/실제 binary digest·native 결과를 read-back했고 각 check의 정확한 run/attempt와 결과를 연결했다. +- [ ] 검증용 signing·unsigned/simulator·device build·실제 native execution·물리 RC·store 배포를 분리한다. 사용자/다른 QA 데이터 및 기기는 보존한다. +- [ ] T01/T02 미제공 필수 entrypoint나 runner 가용성 결여가 없다. 있다면 부분 구현으로 기록하고 R02는 OPEN이다. +- [ ] R01/R04/R09에 실제 check/manifest 계약을 전달했다. 해당 이슈의 별도 미완료 물리/보호/배포/network 기준을 완료로 바꾸지 않았다. + +제외: Flutter/지원 OS의 임의 업그레이드, macOS/Web을 제품으로 추가, 새 서버·로그인·cloud analytics, store 제출/배포·서명 권한 확보를 CI 통과로 주장, 사용자 기기 초기화, 무한 OEM/OS 행렬, compile만으로 OS 수신·picker·암호화 실행 증명. R02 완료는 PR CI 배선/실행의 완료이며 모든 앱 개선 완료가 아니다. + +## grill-with-docs 실제 전체 문답 + +아래는 전담 agent와 root의 실제 질문·답변이다. root가 사용자를 대신해 결정했으며 문서에 맞춰 소급 합성한 대화가 아니다. + +### Q1 — PR 실행 범위와 기존 검사의 재사용 + +**Agent:** +> R02 grill-with-docs 및 고정 d73c7863 조사했습니다. Q1 — PR 필수 실행 범위를 다음처럼 확정할까요? 권고: 기존 Flutter Testing(analyze/unit/widget/coverage80)과 Android Notification Contract(release APK·merged/packaged manifest·일회용 서명)를 유지·재사용하고, R02는 iOS unsigned Release device-target build, 기존 RunnerTests의 실제 simulator XCTest, T02 Android instrumented/native suite, T01 Android emulator/iOS simulator의 결정적 core smoke를 같은 PR 소스에 배선합니다. build 성공을 native test 실행으로 세지 않고, T01/T02 entrypoint 미구현은 명시 dependency pending으로 두어 no-op/skip green을 만들지 않습니다. PR에서는 플랫폼당 대표 runtime으로 bounded 검증하고 T02의 API23/26/31~32/33+/target, iOS15/pre26/26+ 위험 경계 전체와 물리 RC는 별도 수동·확장 run으로 연결합니다. iOS SDK는 기존 AlarmKit guarded 소스를 컴파일 가능한 Xcode26 계열 가용 runner를 명시 확인하고, Flutter3.44.4/Dart3.12.2·지원하한·local-only 정책은 고정합니다. 서명/physical/store 제출은 R02 build 통과로 주장하지 않으며 R04는 같은 소스 배포, R01은 required-check enforcement, R09는 최종 release artifact/network를 소유합니다. 새 glossary/ADR는 불필요합니다. + +**Root:** +> Q1 채택. 기존Flutterquality/AndroidreleaseAPKmanifest를재사용하고 같은PR소스에 iOS unsigned Release device-targetbuild·실제SimulatorXCTest·T02Androidnative·T01양플랫폼deterministiccore배선. no-op/skip을새requiredjob성공처럼표시금지, entrypoint미구현은dependency pending, 단계적으로실제job만완료근거. PR플랫폼당대표runtime으로유한실행,구OS경계확장/물리RC는T02명시run. Xcode26계열 runner실제가용성과SDK·runtime목록확인하여고정; 없으면가용성한계명시하고미확인label만써서실행가능주장금지. Flutter3.44.4/Dart3.12.2/지원하한/local-only고정. unsigned build는서명/설치/RC/deviceproof아님. R01/04/09책임구별, 새glossary/ADR없음. test artifacts와release배포artifact혼동방지. + +### Q2 — Trigger·fork·필수 검사·실행 비용 + +**Agent:** +> R02 Q2 — fork·trigger·required check·실행 비용 정책을 확정하겠습니다. 권고: main/release/**/hotfix/** 대상 pull_request와 해당 branch push, workflow_dispatch를 유지하고 merge_group checks_requested를 함께 지원하되 merge queue 활성화 자체는 R01로 둡니다. 모든 일반 PR(문서 변경 포함)에 안정적인 고유 check 이름을 내고 path filter/draft skip으로 필수 검사를 누락시키지 않습니다. 기본 contents:read, checkout persist-credentials:false, OIDC/store/signing secrets·보호 environment 사용 없음; Android는 현재 disposable key, iOS는 unsigned/simulator, fork도 동일 코드 경로이며 승인 대기는 pass가 아닙니다. pull_request_target 또는 권한 높은 workflow_run으로 untrusted head를 실행하지 않습니다. 최종 mobile-ci 판정은 always 실행해서 필요한 실제 job 전부 success + 예상 suite/case/result 존재를 확인하고 cancelled/skipped/missing/0 tests를 failure 또는 incomplete로 처리합니다. 기존 두 workflow check를 함께 요구할 R01 목록을 문서화하며 cross-workflow 결과를 조회하지 않은 aggregate가 전체quality까지성공했다고하지 않습니다. workflow+PR/ref 단위concurrency로 오래된 PR run만 취소, 플랫폼별 fail-fast:false·유한 job timeout(초기 Android release35분 유지/native45분/iOS60분/smoke45분 목표), 부팅·테스트에도짧은별도watchdog·always diagnostics/소유target cleanup. 자동 flaky retry·continue-on-error는두지않고 재실행은원실패를보존합니다. 새로운mobilejobs의안전한실행설정은R02, deploytrack경쟁/환경보호는R05에남기는안입니다. + +**Root:** +> Q2 채택. main/release/**/hotfix/** PR/push+dispatch,merge_group checks_requested지원(큐활성화는R01). stable고유checks,requiredpaths/draftskip없음. contents:read/persistcredentialsfalse,prodsecret/OIDC/env권한없음,disposableAndroid/unsignediOS. fork동일경로·승인대기pass아님,PR_target/권한높은workflow_run에untrusted실행금지. aggregate always에서실제필수job성공+예상suite/result존재/0tests거부; workflow밖Flutter/Android기존checks는R01에서각각요구,조회없는globalpass주장금지. concurrency는workflow+PR/ref이지만cancel-in-progress는이전PR/merge검증작업에한정하고수동/main/release검증취소정책은명시,배포작업절대이그룹으로묶지않음. failfastfalse/유한timeout Androidrelease35 native45 iOS60 smoke45는초기상한목표,실측따라과도중복없이조정. boot/test watchdog·always실패artifact·소유targetcleanup,실제반환안된제품owner를테스트편의조기release금지. flaky재시도/continueonerror없음. 새CI안전기본R02,배포경쟁/환경R05 유지. + +### Q3 — Artifact·동일 소스·실패 전달·종결 + +**Agent:** +> R02 Q3 — artifact·동일 SHA·negative 검증과 종결을 정하겠습니다. 권고: 각 job이 PR head SHA/base SHA/실제 checkout merge SHA(또는 push/merge_group SHA), workflow run/attempt/job, Flutter/Dart/Java/Gradle/Kotlin/Xcode·SDK/runtime/runner image, lockfile hash, target/app ID·build mode/signing종류, 실제 빌드/설치 artifact SHA256, 예상/실행/성공/실패/skip case와 result/log hash를 기계 manifest로 남깁니다. test APK/app와 release APK/unsigned device app를 별도 식별하고 product DB/key/password·서명키는 artifact/cache에서 제외; 합성fixture로그/xcresult/JUnit/manifest는성공·실패30일, binary는필요한합성validation만선택보존. 필수result누락/업로드실패는기능PASS와구별하여gate실패,부팅전실패는missing진단이있어도통과불가. lockfile/OS/arch/toolchain 기준cache,generated/build/키/data는재사용안하고cold/warm각1회로의존성감춤확인. 일회성격리검증에서 Swift compile failure·native assertion failure·zero-test/missing report·필수jobskip/cancel·smoke failure 각각 aggregate를막는지보이고실패원본을보존,제품코드를고장낸상태로게시하지않습니다. 실제원격PR의같은merge소스가두플랫폼build/native/smoke모두통과하고artifact를다운로드해manifest/hash·check이름/결과를read-back한뒤R02CI배선완료;T01/T02물리필수행,R01보호,R04배포,R09네트워크는자체완료조건을유지합니다. 참고:공식runner-images 현재목록에는macos26/Xcode26.5/26.6/iOS26.5runtime가있으나우리repo실제할당·부팅증거는아직없어구현시재확인예정으로씁니다. 이기준으로문서동결할까요? + +**Root:** +> Q3 채택. sourcehead/base/checkoutmerge(또는push/mergegroup)와runattempt/job/도구SDKruntime/runnerimage/lockhash/appID/mode/signing/build·install실물digest/expectedexecutedpassfailskip/resultloghash 명시. 서로다른test/release/unsignedartifact구별,키DBpassword·서명비밀cache/artifact금지,합성로그/manifest/xcresult/JUnit30일보존;binary는실제필요검증본만. assertionPASS여도필수증거upload/readback실패는전체gate성공아님. caches는dependency전용OSarchtoolchainlockkey,generated/build/키/data재사용금지,coldwarm각1회근거. negative는격리된fixture/검증환경에서한번씩 compile/assertion/zero-missing/requiredskipcancel/smokefailure 판정확인,실제품고장커밋게시·현재branch깨뜨리기금지. 로컬검증기negative와실제원격실패job증거수준구별, green만으로차단동작증명하지않음. 완료는동일원격PRcheckout소스양플랫폼build/native/smoke성공+artifact다운로드hash/check결과readback. T01/T02기기조건,R01보호,R04배포,R09network는별도. 공식runnerimages목록≠실제할당/부팅증거임명시. 실제문답전체와fixedsource/official읽은근거/행렬/구체AC 동결하세요. + +질문과 답변의 축약 표기는 원문을 보존했다. 본문의 실행 계약은 이 답변을 풀어 쓴 것이다. 새로운 제품 정책·용어는 결정하지 않았으며 상세 이슈 게시·CI 구현·실제 실행과 원격 read-back은 root가 이어간다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index d400f8fb..7cfe9c2b 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -835,16 +835,18 @@ "일반 PR CI는 Ubuntu의 Dart analyze/test뿐이다. Android build는 수동 deploy, iOS build workflow는 없다. Swift/Kotlin·plugin/manifest 결함을 일찍 잡기 어렵다.", "PR에서 Android assemble/release 구성 및 iOS simulator/no-sign build, 중요한 native 테스트. 릴리스 후보에서는 실제 signed artifact smoke." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_r02", + "issue_number": 617, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/617", "decisions_file": "plans/audit-2026-09-23/issues/R02.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 30 + "sequence": 30, + "readback_verified": true, + "issue_body_sha256": "598d5655a13bf712411ea23e113cc93c02dc7b6be030ef0e06dd69b29178d2d9" }, { "audit_id": "R04", From 271fd88dd578ea60868a78fc37beed4af9ba9c8a Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:50:30 +0900 Subject: [PATCH 45/59] docs: record A07 remote validation and R04 release contract --- handoff/audit-2026-09-23-progress.md | 9 + plans/audit-2026-09-23/README.md | 6 +- plans/audit-2026-09-23/a07-ci-validation.json | 42 +++++ plans/audit-2026-09-23/issues/A07.md | 7 + plans/audit-2026-09-23/issues/R04.md | 168 ++++++++++++++++++ plans/audit-2026-09-23/state.json | 20 ++- 6 files changed, 241 insertions(+), 11 deletions(-) create mode 100644 plans/audit-2026-09-23/a07-ci-validation.json create mode 100644 plans/audit-2026-09-23/issues/R04.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index e651e645..a67795ef 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -113,3 +113,12 @@ A07 source d73c7863a01343004ccadcd72064b563c464dd0d: 동결29파일과commitblob T01#615,T02#616 게시하여30/64(34생성전). T02 P03의 schedule-start OS예약과A06 activeperiodic 전달범위를명확히분리하는후속문구검토중. C01#600 /root/implement_c01이workflow/typed결과/UI분리및previewclaim실제Drift회귀구현중이며Flutter독점. C01claim이후OS취소대기중일반writer변경은최종DBtransaction revision검사로overwrite방지하되기수행OS부작용을receipt에보존한다. 완전한writer fencing/staging/runtime전이는A09통합미완료로명시한다. /tmp/c01-review-transcript.md에실제문답기록. PR본문갱신은자동승인검토의거절로사용자승인대기중이며우회하지않는다. 로컬준비본문 /tmp/ontime-audit-pr.md. 해당한정승인대기와무관한승인된구현/상세이슈작업계속. + + +PR 승인 대기 본문은 질문 당시 내용(A06원격CI+28개 이슈)으로 고정했다. `/tmp/ontime-audit-pr.md`와 `/tmp/ontime-audit-pr-approval-20260924.md` SHA256 `107246ced5bc0a88cff9f191ccdefcd5d89373c873fb28e92af46402d45d9d41`. 이후 A07/30개 반영 초안은 별도 `/tmp/ontime-audit-pr-next.md`에 두었다. 현재 승인 응답이 없으며 거절된 PR 본문 쓰기를 재시도/우회하지 않는다. + +현재 local HEAD3771615dd2b41089330ba74277d85451d4421c4f(R02문서),remote28baf6c7b91c00d2c6490f597375c1dad39fc2b6(A07증거). A07원격Flutter35947757749는944tests/analyze·88.70%(13294/14987)success,Android35947757745진행중. R02#617게시31/64;R04grill실제3Q답변후문서작성중. + +C01 수정전 음성검증: baseline3771615d에서 preview후17분으로편집한durable값이restore후0분으로덮여실패. agent가BackupService/gate두파일을본인WIP해시백업후일시교체/원복했고사전사후해시일치 /tmp/c01-negative-proof.json 보존. root소스검토재개가능. C01Flutter독점/확장focused/캡처진행중. + +A07원격최종28baf6c7 양CI성공: Flutter35947757749=944/analyze88.70%; Android35947757745 artifact10786864301 synthetic5d57fde334649f0d3cdc1036f54e00d3e6cca431/APKc060bad43f3d66d5337bfc8f934ba0f3f3070ab369361ab8c39df9ed1cf6db29. a07-ci-validation.json 및 #599본문갱신. 기기/merge미완료. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index d82b7668..0bf0900f 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -28,7 +28,7 @@ | A15 | [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 요청별 후속 재조정/교체 직렬화 구현, 최종 원격795 tests/analyze·87.66%·APK 통과, 기기 대기 | | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 독립 journal·복구/host kill11, main통합 원격882 tests/88.50%·APK 통과,기기 대기 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | d06474d4 실제 타이머 전환 구현,47경계 및 원격929전체·88.79%·APK 통과,기기 대기 | -| A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | d73c7863 원자적 시작·partial retry 구현,944 tests/88.67%·KOEN200% 검증,CI·기기 대기 | +| A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | d73c7863 원자적 시작·partial retry 구현,원격944 tests/88.70%·APK 및 KOEN200% 검증,기기 대기 | | C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 실제3문답·staging/gate 보충 포함, 구체 workflow·부분 결과·통합 의존성 확정 | | A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 실제4문답·원자 저장/조회·충돌/멱등/소유권 기준 확정 | | A09 | [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | 실제4문답·staging/runtime 세대·복원 후 정리 기준 확정 | @@ -50,7 +50,9 @@ | R02 | [#617](https://github.com/DevKor-github/OnTime-front/issues/617) | 실제3문답·동일 PR 소스 양모바일 build/native/smoke·최소권한/실패판정·12행 CI 검증 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **31/64개**를 게시하고 원격 본문 동일성을 확인했으며 **33개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200%를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +| R04 | [#618](https://github.com/DevKor-github/OnTime-front/issues/618) | 동일 품질 재사용·검증 AAB와 전송 바이트 연결, 16개 검증 행렬과 실제 3문답 확정 | + +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **32/64개**를 게시하고 원격 본문 동일성을 확인했으며 **32개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/a07-ci-validation.json b/plans/audit-2026-09-23/a07-ci-validation.json new file mode 100644 index 00000000..7a9658eb --- /dev/null +++ b/plans/audit-2026-09-23/a07-ci-validation.json @@ -0,0 +1,42 @@ +{ + "head_commit": "28baf6c7b91c00d2c6490f597375c1dad39fc2b6", + "runs": [ + { + "conclusion": "success", + "databaseId": 35947757749, + "event": "pull_request", + "headSha": "28baf6c7b91c00d2c6490f597375c1dad39fc2b6", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35947757749", + "workflowName": "Flutter Testing", + "tests_passed": 944, + "coverage": { + "percent": 88.7, + "lines_hit": 13294, + "lines_found": 14987 + } + }, + { + "conclusion": "success", + "databaseId": 35947757745, + "event": "pull_request", + "headSha": "28baf6c7b91c00d2c6490f597375c1dad39fc2b6", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35947757745", + "workflowName": "Android Notification Contract", + "artifact_id": 10786864301, + "identity": { + "commit": "5d57fde334649f0d3cdc1036f54e00d3e6cca431", + "artifact": "build/app/outputs/flutter-apk/app-release.apk", + "sha256": "c060bad43f3d66d5337bfc8f934ba0f3f3070ab369361ab8c39df9ed1cf6db29", + "signing": "disposable CI validation key; not a store release", + "merged_manifests": [ + "build/app/intermediates/merged_manifest/release/outputReleaseAppLinkSettings/AndroidManifest.xml", + "build/app/intermediates/merged_manifest/release/processReleaseMainManifest/AndroidManifest.xml", + "build/app/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml" + ], + "device_delivery_verified": false + } + } + ] +} diff --git a/plans/audit-2026-09-23/issues/A07.md b/plans/audit-2026-09-23/issues/A07.md index 3820c6cf..d0bbd77f 100644 --- a/plans/audit-2026-09-23/issues/A07.md +++ b/plans/audit-2026-09-23/issues/A07.md @@ -243,3 +243,10 @@ root: implement_a07: > ARB 전체 재직렬화는 원본 git 내용을 복원한 뒤 신규 2키만 추가하여 최소 diff로 정리했습니다. focused 초기 81 pass/3 UI 실패 확인 중입니다. 실제 DB negative 3건은 수정 뒤 통과했고 singleflight 실패·동일 시각 retry, snapshot partial 후 skip 보존, 새 generation 독립 회귀도 통과했습니다. + + +## 최종 원격 CI 결과 + +HEAD `28baf6c7b91c00d2c6490f597375c1dad39fc2b6`의 [Flutter run35947757749](https://github.com/DevKor-github/OnTime-front/actions/runs/35947757749)이 **944 tests/analyze**, coverage **88.70% (13294/14987)**로 성공했다. 같은 HEAD의 [Android run35947757745](https://github.com/DevKor-github/OnTime-front/actions/runs/35947757745)도 release APK 및 merged/packaged manifest 검사를 통과했다. + +artifact `10786864301`의 synthetic merge `5d57fde334649f0d3cdc1036f54e00d3e6cca431`, APK SHA256 `c060bad43f3d66d5337bfc8f934ba0f3f3070ab369361ab8c39df9ed1cf6db29`를 다시 읽어 확인했다. CI 임시 서명이며 store release와 실제 기기 수신 증거가 아니다. 원격 coverage와 로컬88.67%는 각각 기록한다. `plans/audit-2026-09-23/a07-ci-validation.json`에 전체 식별자를 보존했다. 실제 기기 수용조건과 병합이 남아 OPEN을 유지한다. diff --git a/plans/audit-2026-09-23/issues/R04.md b/plans/audit-2026-09-23/issues/R04.md new file mode 100644 index 00000000..73dfe0b0 --- /dev/null +++ b/plans/audit-2026-09-23/issues/R04.md @@ -0,0 +1,168 @@ +# [P1][R04] PR·Android 배포 품질 검사를 재사용하고 검증한 AAB와 실제 전송 바이트를 연결 + +## 문제와 결과 목표 + +일반 PR은 coverage 80%와 Android release APK manifest 검사를 통과해도, 수동 Play 배포는 그 검사를 동일하게 요구하지 않는다. 두 배포 workflow가 각각 분석과 테스트를 복제하고 별도 AAB를 만들어 전송하므로 **같은 소스의 검사가 성공했다는 사실과 전송하는 바이너리를 검증했다는 사실**이 연결되지 않는다. + +R04는 PR·push·dispatch·두 Android 후보 배포가 동일한 품질 정의를 사용하게 하고, 그 품질을 통과한 정확한 소스로 만든 AAB를 검증한 뒤 **검증한 바이트만 업로드 경계에 도달하도록** 만든다. 단순 coverage 명령 추가에 그치지 않으며 기존 테스트 전체를 배포 단계에 또 복제하지 않는다. + +- 우선순위 **P1**. 전담 agent `/root/grill_r04`, 적용 skill `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +- 고정 조사 source **`28baf6c7b91c00d2c6490f597375c1dad39fc2b6`**. 병행 C01 WIP는 이 조사에 포함하지 않았다. +- 실제 root 문답 3개와 마지막 보충 정정을 아래 원문으로 보존한다. root는 사용자에게서 위임받은 결정자로 답했다. +- 이번 작업은 고정 코드·기존 이슈·공식 문서를 읽고 이 이슈 본문만 작성했다. Flutter·native·배포 검증을 실행하거나 workflow/제품/설정/스토어를 변경하지 않았다. 비밀 값을 읽지 않았다. +- **현재 상태: 설계 완료, R04 구현·새 검증 미실행.** 기존 CI 성공을 아래 신규 AC 성공으로 합산하지 않는다. + +## 고정 근거와 감사 baseline 정정 + +| 근거 | 실제 현재 사실 | R04에서 남은 간격 | +|---|---|---| +| [Flutter Testing](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/.github/workflows/flutter_test.yml) | Flutter3.44.4, generator, generated/local-only policy, source notification contract/negative fixtures, analyze, `flutter test --coverage`, coverage80이 있다. | 두 Play 경로에서 동일 정의를 재사용하지 않으며 현재 이 workflow에는 배포의 tracked drift 검사도 없다. 양쪽 중 약한 쪽으로 맞추지 않고 필요한 검사를 합친다. | +| [Internal workflow:18](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/.github/workflows/android-play-internal.yml#L18) | main에서 dispatch, staging environment, generator/policy/tracked drift/analyze/`flutter test`, signed AAB, internal draft 전송. | coverage gate·원본 report·동일 native/smoke gate가 없다. test 전 키를 해제하며 한 job이 품질·서명·전송을 모두 맡는다. | +| [Closed workflow](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/.github/workflows/android-play-closed.yml) | release/** 또는 hotfix/** dispatch, alpha track, internal과 사실상 복제된 검사/빌드 경로다. | 같은 품질 간격과 결합 문제가 있다. job 이름의 release가 실제 공개 출시를 뜻하지는 않는다. | +| [Android Notification Contract](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/.github/workflows/android-notification-contract.yml) | PR/push/dispatch에서 release APK를 빌드하고 packaged/merged manifest, digest를 기록한다. contents:read·concurrency·35분 timeout도 있다. | 일회용 검증 키의 APK이고 `device_delivery_verified: false`다. 업로드 AAB와 같지 않고 실제 수신·앱 smoke를 입증하지 않는다. | +| [Upload script:33](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/.github/scripts/upload_google_play_draft.js#L33) | `RELEASE_FILE` 존재를 확인해 stream으로 전송하고 draft track update와 edit commit을 수행한다. | quality manifest/digest/source 검증 없이 경로를 신뢰한다. 존재 확인과 나중 stream open 사이 바이트 동일성도 별도 검증이 없다. commit 후 authoritative read-back은 R07 책임이다. | +| [Android Gradle:89](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/android/app/build.gradle#L89) | release는 명시 signing config를 요구하며 package는 club.devkor.ontime, version은 Flutter build inputs다. | 후보 AAB의 실제 package/version/certificate를 예상 입력과 비교하고 검증 서명과 배포 서명을 구별해야 한다. | +| [Coverage checker](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/tool/check_coverage.dart), [ADR0024](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/docs/adr/0024-enforce-no-network-in-product-builds.md) | app-owned coverage 80%와 strict Product Network Boundary가 기존 정책이다. | threshold를 낮추거나 QA를 위해 release network/client를 추가하지 않는다. | + +감사 초기의 “일반 PR은 Dart뿐이며 Android build가 없다”는 현재 사실이 아니다. Android release APK/manifest CI는 이미 존재한다. “모든 workflow에 concurrency/timeout이 없다”도 위 Android 검사에는 해당하지 않는다. **두 수동 Play 배포의 coverage 및 검증 artifact 연결 부재**가 R04의 현재 문제다. main 보호/effective rules의 현재 원격 상태는 여기서 새로 조회하지 않았으므로 현재 확정 사실로 반복하지 않는다. R01에서 fresh read-back한다. + +고정 workflow blob: Flutter `4ebd3b3a7be134e454056f72209d0b287ac681d8`, Android contract `2fd55cefdbb3b3246d0f26127cdddc12a84bcf6d`, internal `bdd75b90e3e2318c452a16a05dd20814daf1487b`, closed `43f805923b2d23d91cf215a2c6f15af4745618f8`, uploader `a14d17d729649c3f957dec80466e65d498ad3891`. + +## 공식 문서로 확인한 기술 경계 + +- 저장소 내부 reusable workflow 상대 참조는 caller와 같은 commit의 정의를 사용한다. caller/checkout SHA를 기록하고 movable branch 이름으로 검증 소스를 다시 선택하지 않는다. [GitHub workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax) +- reusable workflow의 environment secret 동작과 caller secret 전달은 같지 않다. 품질 workflow에 environment 또는 `secrets: inherit`를 넣지 않고 privileged job을 분리한다. [GitHub reuse workflows](https://docs.github.com/en/enterprise-cloud%40latest/actions/how-tos/reuse-automations/reuse-workflows) +- upload-artifact v4 artifact는 immutable이며 job 간 전달이 가능하다. download의 digest 불일치는 문서상 경고이므로 그 경고만으로 차단을 보장하지 않고 파일 SHA256을 독립 검사한다. [GitHub artifacts](https://docs.github.com/en/actions/tutorials/store-and-share-data) +- bundletool은 AAB에서 APK set을 생성해 지정 기기에 설치할 수 있다. 서명정보 미지정 시 debug key를 시도하므로 검증 서명을 명시한다. [Android bundletool](https://developer.android.com/tools/bundletool) +- AAB upload key와 Google Play의 사용자 배포 app signing key를 구분한다. 로컬 파생 APK 성공은 Play가 서명한 split/update 검증이 아니다. [Android app signing](https://developer.android.com/studio/publish/app-signing) + +이 문서는 위 문서를 설계 근거로 읽었으며 해당 기능이 이 저장소에서 이미 실행된다고 주장하지 않는다. action/tool 버전 pin 전체 정비는 R06과 조정한다. + +## 확정 계약 + +### 1. 하나의 품질 정의와 정확한 소스 + +1. PR/push/dispatch와 기존 internal(main)/closed(release/**·hotfix/**) 경로가 같은 reusable quality entrypoint를 호출한다. 구성상 하위 workflow로 나누더라도 필수 suite/판정 정의는 하나이고 wrapper별 느슨한 사본을 두지 않는다. +2. Flutter **3.44.4**, Dart **3.12.2**, Java17 및 저장소 lockfile에 맞춘 의존성, generator, generated policy, local-only policy, tracked drift, analyze, full unit/widget coverage80, source manifest/negative contract를 포함한다. R02가 제공하는 양 모바일 build/native/core smoke도 동일 소스 gate에 연결한다. +3. `git rev-parse HEAD`, PR head/base/실제 merge SHA, event, workflow/run/attempt, lockfile hashes와 toolchain을 명시한다. dispatch가 선택한 SHA는 실행 내내 고정한다. 실행 중 branch가 움직여도 checkout을 latest로 바꾸지 않는다. PR synthetic merge 성공을 head 또는 main 성공으로 바꿔 적지 않는다. +4. 일반 PR에는 signing/store secret을 제공하지 않는다. fork 승인을 기다리는 상태는 미실행이며 통과가 아니다. `pull_request_target` 또는 privileged `workflow_run`으로 untrusted 코드를 우회하지 않는다. +5. deploy는 같은 run DAG의 필수 결과가 모두 성공했을 때만 서명/build 단계로 진입한다. 다른 run의 이름이 같은 green 또는 과거 성공만 보고 통과시키지 않는다. 필수 suite/report/identity가 없거나 fail/skipped/cancelled/0 cases/dependency pending이면 업로드 불가다. +6. stable check 이름과 기존 PR 실행 범위는 유지하거나 R01 required-check 목록과 원자적으로 조정한다. R02가 정한 merge_group 경로도 재사용 가능한 정의를 사용한다. 전체 workflow skip/path-filter 때문에 필수 check가 사라지게 만들지 않는다. +7. 동일 run에서 품질 검사를 중복 실행하지 않는다. 후보와 정확히 같은 source/mode/toolchain의 재사용 가능한 build만 identity 검증 후 공유한다. 별도 main 후보 SHA의 검증 필요성을 중복이라며 생략하지 않는다. + +**보충 정정:** 정상적인 ignored generated Dart 및 build 출력 생성 자체가 tracked drift는 아니다. 검증 소스·lock/generator 입력·tracked 파일 정책을 판정하며, 검증 후 소스/입력/실제 전송 바이트가 바뀌는 경우를 차단한다. ignored generated 파일은 계속 재생성하고 커밋하지 않는다. + +### 2. 검증한 AAB에서 최종 전송까지 + +| 단계 | 필수 입력/검증 | 생산 비밀·허용 효과 | +|---|---|---| +| quality | 정확 SHA, lock/toolchain, 필수 suite/report 및 coverage | 생산 비밀 없음, 검사만 | +| signing/build | quality receipt, 동일 SHA/입력, 명시 versionName/versionCode | upload signing key만 필요한 단계에 제공; AAB 1회 생성 | +| candidate inspection | 실제 AAB package/version/signing certificate/SHA256, packaged manifest | 공개 certificate 식별자만 증거에 포함; 키·암호 없음 | +| AAB-derived smoke | 그 AAB digest로부터 bundletool APK set, 각 설치 split digest/device spec/검증 서명, T01 release-compatible core smoke | production 비밀 없는 소유 emulator, 명시 validation key, 실제 해당 binary 외부 native UI driver | +| final guard | 같은 run의 고유 artifact ID, AAB 파일 hash, source/inputs/version/cert, 필수 quality 및 smoke receipt | 불일치면 전송 경로 진입 금지 | +| upload | 검증한 bytes에 고정한 파일/stream, 허용 package/track | Play 권한만; 빌드·재생성하지 않음 | + +- AAB 검사 후 별도 `flutter build apk`로 만든 binary를 테스트해 AAB smoke라고 하지 않는다. test용 main/channel/network 권한을 release에 추가해 다른 binary를 만들지도 않는다. T01 driver가 해당 release-compatible 경로를 지원하지 않으면 dependency pending이다. +- AAB→APK set→설치된 split의 관계를 기록한다. 원 AAB, 파생 APK, GitHub artifact archive의 digest는 서로 다른 값이며 혼동하지 않는다. app ID/version/ABI/OS/device spec/서명 종류를 포함한다. +- 전송 job은 AAB를 다시 빌드하지 않고 같은 run의 정확한 artifact ID를 내려받는다. 파일명·artifact 이름·latest successful만으로 선택하지 않는다. missing/expired/digest/source/lock/toolchain/version/certificate 불일치면 uploader 호출 **0회**다. +- 검사와 stream open 사이 파일 교체를 막는다. 구현은 검증된 복사본 또는 동등한 바이트 고정 방식을 사용하고, 전송하는 stream의 바이트가 검증된 바이트와 같음을 부정 테스트로 입증한다. 존재 확인 후 나중 path를 다시 여는 방식만으로 끝내지 않는다. +- candidate metadata가 합성되었거나 wrapper 출력에만 존재하는 상태로 통과시키지 않는다. 실제 AAB에서 읽은 package/version/cert를 예상 입력과 비교하고, 검증 결과의 SHA/identity도 교차 확인한다. +- local validation APK 서명은 Play App Signing과 다르다. 이 결과는 로컬 파생 candidate smoke이며 실제 Play 설치·동일서명 업데이트·사용자 배포 완료는 별도 T02/R07/R08 증거다. + +### 3. 권한·재시도·진단 + +- quality에 생산 비밀0, signing/build에 upload signing만, smoke에 생산 비밀0, uploader에 Play credential만 제공한다. 기존 staging의 실제 보호 설정은 R05에서 확인한다. job 분리만으로 원격 환경 보호가 적용됐다고 하지 않는다. +- 키/암호/서비스계정 JSON/사용자 DB/앱 데이터는 로그·cache·artifact에 넣지 않는다. 필요한 임시 키는 가장 늦게 만들고 always cleanup한다. runner 강제 종료에서 cleanup hook 실행을 보장한다고 하지 않는다. +- source/workflow/run/attempt, artifact ID/file digest, lock/toolchain/version/cert 식별자, 필수 suite 및 coverage numerator/denominator/threshold, derived split/device metadata, 원본 결과/log hash, failure reason을 기계 판독한다. signing 종류와 `store_write_not_run`을 분리 기록한다. +- rerun은 원 실패와 새 attempt를 모두 보존한다. 같은 run 재실행도 실제 artifact identity와 필수 결과를 다시 확인한다. 다른 SHA면 전체 검증, 새 version 또는 rebuild면 새 candidate로 artifact smoke도 다시 한다. 단순 “latest green”은 재사용 조건이 아니다. +- R04는 자동 Play 재전송을 추가하지 않는다. version 중복·전송 후 응답 상실·commit 미확정은 R05/R07의 별도 멱등성/상태확인 경계이며 성공으로 추정하지 않는다. +- 입력 branch/package/track 허용 범위를 그대로 유지한다. 자동 production 배포 trigger나 tag 승격을 만들지 않는다. 현재 draft 전송과 사용자 공개 배포는 구분한다. + +## 구현 순서와 실패 검증 행렬 + +1. 현재 workflow/check 이름·동일 소스/입력 정의를 고정하고 R02/T01/T02 entrypoint 및 필수 결과 schema 상태를 확인한다. +2. 기존 Flutter/Android 검사를 reusable 품질 정의로 옮겨 양 PR·deploy wrapper가 호출하게 한다. coverage80 및 tracked drift 어느 쪽도 잃지 않는다. +3. production secret 없는 quality, signing/build, AAB-derived smoke, final guard/upload를 분리하고 정확 artifact 전달을 연결한다. +4. 실제 최종 guard와 file-open/전송 경로를 공유하는 no-store 검증 entrypoint를 만든다. 합성 credential과 local fake transport로 sink에 도달한 bytes를 독립 캡처한다. 상위 job을 건너뛰고 success만 찍는 dry-run은 금지한다. +5. 아래 negative의 guard fixture와 실제 workflow 연결을 각각 검증한다. 실제 compiler/test 실패가 gate로 전달되는 R02 증거는 재사용하고, 별도 fixture 성공을 실제 원격 native 실패 run으로 부르지 않는다. +6. 원격 동일 SHA의 양 wrapper validation, 실제 AAB-derived smoke, artifact 다운로드/재해시를 수행한다. 생산 서명 경로 확인 여부를 별도로 기록한다. 실제 스토어 write는 이 설계의 실행 허가에 포함되지 않는다. + +| ID | 자극 | 기대 결과와 독립 관측 | +|---|---|---| +| R04-01 | PR와 internal/closed wrapper 정상 fixture | 같은 quality 정의 및 필수 suites 호출, 각 고정 checkout 기록. candidate는 최종 local sink bytes와 SHA256 일치. | +| R04-02 | coverage 79.99% / 정확80% / report 없음·app-owned0 | 79.99와 missing/0는 실패·upload0; 80은 coverage 조건만 통과하고 나머지 gate 유지. 표시 반올림으로 경계가 바뀌지 않음. | +| R04-03 | generator가 tracked 파일 변경, 정상 ignored 출력 생성 | tracked drift 차단; 정상 ignored 생성은 허용. generated 파일 커밋으로 문제를 숨기지 않음. | +| R04-04 | analyze/source contract/native/core smoke 실패 | 해당 실패 유지, upload0, 원 진단·반대 플랫폼 결과 보존. | +| R04-05 | 필수 suite0/report 누락/job skip·cancel·pending | exit0 또는 aggregate 이름이 있어도 전체 성공 불가·upload0. | +| R04-06 | dispatch 뒤 branch 이동 또는 PR merge/head 바꿔치기 | 원 immutable SHA 유지; provenance 다른 결과 결합 거부. source 변경 시 기존 gate 재사용 금지. | +| R04-07 | lock/toolchain/version/package/cert 예상값과 실제 AAB 불일치 | 이름만 같은 candidate 거부·upload0, 공개 식별자 범위의 진단. | +| R04-08 | artifact missing/expired/다른 run 또는 attempt/이름 충돌 | 정확 identity 없으면 upload0; 다른 artifact로 fallback하지 않음. | +| R04-09 | 다운로드 bytes 손상, artifact warning만 반환 | 독립 hash 검사 비정상 종료·upload0. 경고를 pass로 취급하지 않음. | +| R04-10 | 최종 guard 전후 파일 교체·symlink/path 교체 | 검증된 bytes만 전송하거나 실패·upload0. 교체 파일이 sink에 도달하지 않음. | +| R04-11 | signed AAB 생성 또는 packaged manifest 검증 실패 | 파생 smoke/전송 진행 불가, 임시 비밀 cleanup 및 실패 진단; secret 없는 품질 로그 유지. | +| R04-12 | 별도 APK 또는 test 전용 binary로 smoke 결과 대체 | 원 AAB→derived split 연결 실패·upload0; 실제 candidate 재검증 필요. | +| R04-13 | 정상 signed AAB→실제 소유 emulator 파생 split 설치 | T01의 release-compatible smoke와 digest chain 일치; Play delivery PASS는 기록하지 않음. | +| R04-14 | 같은 run rerun, 새 version/rebuild | 원 실패/attempt 유지; identity 같고 모든 필수 결과 확인된 경우만 재사용. 새 candidate는 smoke 새 실행. | +| R04-15 | fork/PR 품질 및 서명·smoke·전송 job 권한 | PR/quality/smoke 생산 비밀0, 서명과 Play 권한 분리, artifact/cache 비밀 없음. 실제 비밀 값을 출력하지 않고 wiring 및 합성 sentinel로 확인. | +| R04-16 | 전송 뒤 응답 미확정·재시도 요청 | R04가 자동 재전송/성공 처리하지 않음. R05/R07 상태확인으로 연결, 기존 실패 보존. | + +필수 binary smoke/native 기기 행을 host mock으로 바꿔 green을 만들지 않는다. exact source/fixture/method/원실패/수정 후 결과를 증거에 기록한다. 기존 사용자 기기·다른 작업 simulator·production DB를 수정하지 않고 소유 disposable target과 합성 데이터만 사용한다. + +## 의존성과 책임 경계 + +| 이슈 | 재사용·인계 내용 | +|---|---| +| [T01 #615](https://github.com/DevKor-github/OnTime-front/issues/615) | 실제 앱 core journey/외부 native UI driver 및 release-compatible smoke entrypoint·결과 schema. R04가 새 QA 앱을 만들지 않음. | +| [T02 #616](https://github.com/DevKor-github/OnTime-front/issues/616) | 실제 native 계약·물리 RC/provider/서명 update 행렬. AAB-derived emulator 성공과 물리/Play 성공 구별. | +| [R02 #617](https://github.com/DevKor-github/OnTime-front/issues/617) | PR 모바일 runner/build/native/smoke wiring. R04는 그 필수 결과를 동일 품질 정의와 배포 DAG에 연결. | +| R01 | main/release 보호·effective rules·required check 목록/merge queue. R04의 local workflow 변경만으로 원격 보호 적용 주장 금지. | +| R05 | Play package/track 직렬화, staging 보호, 시간 제한·중단/중복 실행 정책. R04 job 분리의 권한 경계와 조정. | +| R06 | action/tool pin 및 Google uploader lockfile/설치 재현성. R04 identity에 실제 버전/lock hash 포함하되 전체 pin 정비 중복 금지. | +| R07 | track 기존 release 보존, Play commit 후 authoritative read-back, 응답 미확정과 version 중복 처리. R04가 store 성공을 만들지 않음. | +| R08 | 장기 release manifest/보존·symbol·서명·store 연결, 태그/후속 승격·복구 절차 및 iOS 배포. R04 최소 guard identity를 받아 확장. | +| R09 | 최종 APK/IPA SDK·권한·entitlement·offline cold start·앱 runtime traffic 증거. 기존 source/manifest 검사만으로 전체 no-network 완성 주장 금지. | +| O01/O02 | 오래된 Firebase/login/서버 중심 릴리스 문서의 전체 정합성. R04가 만지는 품질·배포 절차는 현재 local-only 정책으로 정확히 설명. | + +새 제품 도메인 용어는 없다. `CONTEXT.md`는 품질 pipeline 구현 명세를 넣는 곳이 아니므로 변경하지 않는다. 이번 재사용/guard 결정은 별도 어려운 제품 정책 trade-off가 아니므로 ADR를 만들지 않는다. 기존 local-only/Android·iOS/ignored generated 정책을 그대로 유지한다. + +## 완료 기준 + +- [ ] PR/push/dispatch 및 두 Android wrapper가 동일 quality 정의를 사용하며 기존 coverage80·source contract·generated/local-only·tracked drift·R02 필수 native/smoke를 모두 유지한다. +- [ ] immutable checkout/PR head/base/merge와 lock/toolchain/run/attempt를 구별하고 다른 소스의 성공을 결합하지 않는다. +- [ ] 품질 실패/skip/cancel/pending/missing/0-case가 signing/upload의 우회 성공으로 이어지지 않는다. +- [ ] 생산 비밀이 quality/smoke로 전달되지 않고 signing/Play job 분리·임시 비밀 cleanup·artifact/cache 제외를 검증했다. +- [ ] 실제 후보 AAB를 한 번 만들고 package/version/cert/hash/manifest 검사 및 **동일 AAB 파생 실제 설치 smoke**를 완료했다. +- [ ] upload는 rebuild 없이 고유 artifact ID+독립 file SHA256을 검증하고, 최종 guard 이후에도 검증한 bytes만 전송한다. +- [ ] no-store 실제 경로에서 정상 bytes 동일성과 R04-02~12 등의 upload0를 독립 관측하고 실패 원본을 보존했다. +- [ ] 원격 양 wrapper validation/공유 quality 및 artifact 재다운로드 재해시 증거를 확보했고 실제 생산 서명 경로의 미검증 부분이 없다. +- [ ] required check 이름과 R01, 나머지 R02/R05~R09의 명시 책임/미완료 상태가 연결돼 있다. +- [ ] 실제 Play write·commit·사용자 배포 상태를 별도 기록하고 미실행을 성공으로 표시하지 않는다. T01/T02/R09 필수 RC 증거가 없으면 전체 release 완료라고 하지 않는다. + +**종결 구분:** reusable workflow가 존재하거나 fixture gate만 통과한 상태는 R04 구현 완료가 아니다. 실제 AAB-derived smoke·원격 연결·생산 서명 경로 중 미검증이 있으면 이 이슈는 OPEN이다. R04 코드 검증이 완료되더라도 Play write의 실행 허가나 store read-back/사용자 배포 완료를 의미하지 않는다. 이번 설계 작업은 실제 업로드·릴리스·원격 보호 변경을 실행하지 않는다. + +## 실제 grill-with-docs 문답 원문 + +### Q1 — 품질 재사용 단위 + +**Agent 질문과 권고:** +> R04 skill·고정 28baf6c7 조사 완료. 두 Play workflow는 generator/policy/drift/analyze/flutter test가 있으나 coverage80·source/packaged manifest negative·native/smoke의 동일 품질 연결은 없고, 키를 test 전에 해제합니다. Android PR release APK/manifest CI는 이미 있으므로 baseline 정정합니다. Q1 — 동일 품질의 재사용 단위를 다음처럼 확정할까요? 권고: PR/push/dispatch와 internal(main)/closed(release·hotfix) 후보가 같은 reusable quality entrypoint(Flutter3.44.4/Dart3.12.2, lockfile·generator·generated/local-only·tracked drift·analyze·full coverage80·source contract, R02 native/build/smoke)를 정확한 checkout SHA로 호출합니다. dispatch가 잡은 immutable SHA를 고정하고 PR synthetic merge 성공을 head/main 성공으로 대체하지 않으며, 이전 다른 run의 이름만 같은 green을 재사용하지 않습니다. deploy는 같은 run DAG의 필수 품질 성공 후만 signing/build, test를 deploy 본문에 복제하지 않습니다. missing/skip/cancel/dependency pending은 upload 불가이고 새 필수suite 준비 전 완료 처리하지 않습니다. shared workflow는 PR에 secrets 없이 실행, production signing/store secret은 성공 뒤 최소 job에만 제공. 기존 stable check 이름·R01 목록과 조정하며 R02가 suite/runner, R04가 재사용/배포 연결을 소유하는 안입니다. + +**Root 답변:** +> Q1 채택. 같은reusablequality entrypoint를PR/push/dispatch 및기존internal main/closed release-hotfix 후보가사용,immutable checkoutSHA고정하고PRsyntheticmerge≠head/main구별. lock/generator/generatedlocalonly/trackeddrift/analyze/fullcoverage80/sourcecontract/R02native-smoke 동일정책,기존green이름만으로재사용금지. deploy같은run DAG필수검증성공후에만최소signing/build/uploadjob,품질복제없고필수pending/missing/skip/cancel이면upload불가. PR품질secret없음;기존검사실행범위/requiredcheck이름R01과연결. R02suite/runner R04reuse/deploylink소유. 현재branch/track의기존허용범위는유지하고자동production배포trigger를새로만들지않음. 이번설계는실제스토어업로드/릴리스실행허가로해석하지않고원격행동은별도명확한요청/기존승인범위로판단. 실제서명된배포artifact 자체의검증은다음결정에서같은소스build와구별해주세요. + +### Q2 — 실제 AAB와 설치·전송 바이트 + +**Agent 질문과 권고:** +> R04 Q2 — 실제 업로드 AAB와 검증 binary 관계를 다음으로 확정할까요? 권고: 품질 통과 뒤 해당 immutable source+lock/toolchain+명시 versionName/versionCode로 signed AAB를 한 번 만들고 그 AAB 자체의 package/version/signing certificate·sha256·필수 packaged manifest 검사를 실행합니다. 동일 AAB로 bundletool 파생 APK set을 만들어 명시 소유 emulator에서 T01 release-compatible core smoke를 수행하고 원 AAB digest→APK set/installed split digests·device spec·검증 서명종류를 연결합니다. 별도 flutter build apk 성공을 AAB smoke로 대체하지 않고 production code에 QA/네트워크 우회를 넣지 않습니다. Play App Signing의 실제 사용자 APK와 로컬 bundletool 서명은 다르므로 local derived smoke≠Play delivery/update proof; 후자는 T02/R07/R08에 연결합니다. 업로드 job은 빌드하지 않고 같은 run의 고유 artifact ID+digest로 AAB를 내려받아 재계산/검사 후 그 파일만 전송, 불일치·missing/expired·version불일치면 전송0회. GitHub download-artifact의 digest 불일치는 문서상 warning이므로 별도 fail-closed 검사를 둡니다. quality는 secret0, signing job은 upload signing만, smoke는 생산비밀0(명시 validation signing), upload는 Play권한만; key/비밀·app 데이터는 cache/artifact 제외하고 always cleanup. R04가 최소 identity 전달과 차단을 만들며 R08 전체 release manifest/보관·R09 SDK/traffic 심층 검증은 중복 구현하지 않는 안입니다. + +**Root 답변:** +> Q2 채택. 품질성공후immutable source/lock/toolchain/명시version으로signedAAB1회생성,그AAB의package/version/cert/digest/packagedmanifest검사. 동일AAB→bundletool APKset→설치split digest/기기spec/validation서명 관계를기록,별도flutterAPK검증으로대체금지. releasecompatible smoke는외부nativeUI driver 등실제해당artifact를조작하는경로여야하고다른test전용binary/QA channel/network추가로완료위장금지. uploadjob재빌드없고동일runartifactID+독립SHA256불일치/missing/expired/version오류면전송0. 다운로드warning만신뢰하지않음. PRquality secret0,signing job uploadkey만,smoke는생산비밀0,uploadjobPlay만leastprivilege·비밀artifactcache금지·cleanup. localderivedvalidationAPK≠PlayAppSigning실사용split/updateproof,후자는T02/R07/R08. R04는필요최소identity검증과차단,R08전체manifest/R09심층network중복없음. store연결없는실행으로도전송직전guard검증가능하게설계,설계완료를실제Play업로드완료로표시하지않음. + +### Q3 — 부정 검증·재시도·종결과 보충 정정 + +**Agent 질문과 권고:** +> R04 Q3 — 실패 시나리오·retry·완료 경계를 이렇게 고정할까요? 권고: coverage79.99/80경계·report누락, tracked drift, native/smoke fail/skip/0case, SHA/lock/toolchain/version/cert불일치, artifact누락·변조·다른run/attempt, dispatch 뒤 branch이동, quality 이후 source재checkout/생성변경, guard이후 파일교체, signing/검증실패에서 uploader호출0회·secret제공경계를 확인합니다. 정상은 양 wrapper가 동일품질을호출하고 동일AAB bytes가 최종전송sink에도달하는 no-store dry-run으로 검증; uploader를 단순 workflow위쪽 bypass하는 dry-run이 아니라 실제 final guard와file-open경로를 공유하고 합성credential+local fake transport를 사용합니다. 경로검증/TOCTOU는 검증된복사본을고정하여 검증한bytes와전송stream이같도록하고, package/track입력은 기존allowlist유지합니다. retry는 run/attempt와 원실패 보존, 품질결과가다른SHA면전부재검증; 같은run rerun도 required결과/실제artifact identity전부확인해만재사용(막연한latest성공금지), 새version/rebuild면새candidate로artifact smoke 재검증. 실제 Play write/retry는 R05/R07의중복version·미확정commit 처리에맡겨R04가자동재전송하지않습니다. R04완료는 shared품질/두wrapper/실제AAB-derived smoke/양negative gate/원격artifact 재다운로드검증까지이고 생산서명경로 미검증이면 OPEN, Play commit·사용자배포는 별도상태; 기기RC/T01T02·R09미완료는 전체release차단으로남깁니다. 새제품glossary/ADR는추가하지않고관련workflow문서만구현시맞춥니다. + +**Root 답변과 보충 정정:** +> Q3 채택. 경계79.99/80·missingreport/drift·native/smoke실패skip0·identity변조/다른runattempt·branch이동·최종guard/file교체·signingfail은uploader0을독립관측. '생성변경'은검증소스/입력/실제배포bytes가검증후달라지는경계이며정상ignoredDart/build생성을무조건drift실패로취급하지말고동일lock/generator입력과tracked drift정책으로판정하세요. no-store dryrun은실제최종guard+검증bytes전송경로공유,합성credential/localtransport만,상위jobbypass성공아님. 같은AAB전송bytes고정/TOCTOUnegative 포함,기존package/trackallowlist유지. rerun은원실패/attempt보존,동일identity/필수결과검증없이는재사용없음,새version/rebuild는newcandidate+artifactsmoke. R04자동Play재전송없고불명확commit/중복version은R05/R07. 완료는remote공유quality/양wrapper/실제AABderivedsmoke/negative/재다운로드hash증거와production서명경로확인까지(실제Playwrite는별도승인범위),미검증은OPEN. T01/T02/R09미완료이면전체release완료주장금지. 충분하면실제전체문답·고정근거·구체행렬·완료/비목표로동결. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 7cfe9c2b..2c47089c 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -428,7 +428,7 @@ "`lib/domain/use-cases/schedule_preparation_session_use_case.dart:84-87`", "실패 시 재시도 가능, 성공 중복 호출은 멱등. 첫 write 실패→두 번째 성공과 동시 호출을 실제 repository 경계에서 검사." ], - "status": "code_verified_ci_device_pending", + "status": "code_verified_device_pending", "agent": "/root/grill_a07", "issue_number": 599, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/599", @@ -439,16 +439,16 @@ "Actual Drift pre-fix three failure cases confirmed; focused56 passed", "Full944 tests/analyze clean; coverage88.67% (13289/14987); Python11/manifest/generated/local-only pass", "29 source/test/QA file freeze and committed blobs independently hash-verified", - "KO/EN 200% error/partial screenshots; list scroll/skip/next-step hit test pass; synthetic receipt screenshots distinct from real SQLite button regression" + "KO/EN 200% error/partial screenshots; list scroll/skip/next-step hit test pass; synthetic receipt screenshots distinct from real SQLite button regression", + "Remote28baf6c7 Flutter35947757749:944/analyze/88.70%(13294/14987); Android35947757745 success, artifact10786864301 digest read back" ], "blockers": [ - "Remote CI for this implementation pending", "Physical Android/iOS lifecycle and delivery acceptance pending" ], "sequence": 13, "readback_verified": true, "body_sha256": "6d58178bbc4ffcfe52875c46537bb8eb3250cd4f9a1f13356bb7a44475065b20", - "issue_body_sha256": "3a5af2d11207eb219ce324b55367fcf5270204bac6c59df67f2fdbe7c1d7d7c7" + "issue_body_sha256": "fd59799ff5a323bedb9a41cb39efa3543aff5fac8f0540384105ad09b467b3ef" }, { "audit_id": "C01", @@ -858,16 +858,18 @@ "Android deploy는 analyze/test를 다시 하지만 coverage gate는 없다. main 보호도 없어 검사 수준이 경로마다 다르다.", "동일 quality workflow 재사용, 같은 SHA의 coverage/native smoke 포함. 중복 테스트를 무작정 늘리지 말고 artifact/provenance를 연결." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_r04", + "issue_number": 618, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/618", "decisions_file": "plans/audit-2026-09-23/issues/R04.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 31 + "sequence": 31, + "readback_verified": true, + "issue_body_sha256": "5341cfaee1a6c1027ae288c64e93fea0fedc937d725987445ee4de4b40186ce9" }, { "audit_id": "R01", From 4529d9f84e911b1ee2fbb42af09698a0c27cc9f2 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:51:17 +0900 Subject: [PATCH 46/59] fix: preserve backup restore outcomes and reject stale previews --- lib/core/backup/backup_service.dart | 118 +++-- .../database/local_data_operation_gate.dart | 11 +- lib/core/database/local_reset_protocol.dart | 25 +- .../services/alarm_ownership_journal.dart | 5 +- .../local_data_workflow_adapters.dart | 73 +++ lib/domain/entities/backup_operation.dart | 69 +++ lib/domain/entities/local_reset_result.dart | 23 + lib/domain/ports/local_data_ports.dart | 18 + .../use-cases/local_data_workflows.dart | 109 +++++ lib/l10n/app_en.arb | 64 ++- lib/l10n/app_ko.arb | 64 ++- lib/l10n/app_localizations.dart | 247 ++++++++++ lib/l10n/app_localizations_en.dart | 144 ++++++ lib/l10n/app_localizations_ko.dart | 132 ++++++ lib/main.dart | 9 +- lib/presentation/my_page/my_data_screen.dart | 421 ++++++++++++------ .../screens/local_data_recovery_screen.dart | 15 +- .../backup_restore_stale_contract_test.dart | 70 +++ .../alarm_replacement_integration_test.dart | 18 +- .../local_data_operation_gate_test.dart | 25 ++ .../use-cases/local_data_workflows_test.dart | 247 ++++++++++ .../my_page/my_data_export_test.dart | 100 ++++- .../my_page/my_data_restore_test.dart | 66 ++- tool/qa/capture_local_data_workflows.dart | 132 ++++++ 24 files changed, 1988 insertions(+), 217 deletions(-) create mode 100644 lib/data/adapters/local_data_workflow_adapters.dart create mode 100644 lib/domain/entities/backup_operation.dart create mode 100644 lib/domain/entities/local_reset_result.dart create mode 100644 lib/domain/ports/local_data_ports.dart create mode 100644 lib/domain/use-cases/local_data_workflows.dart create mode 100644 test/core/backup/backup_restore_stale_contract_test.dart create mode 100644 test/domain/use-cases/local_data_workflows_test.dart create mode 100644 tool/qa/capture_local_data_workflows.dart diff --git a/lib/core/backup/backup_service.dart b/lib/core/backup/backup_service.dart index 975a6977..78ee0c0e 100644 --- a/lib/core/backup/backup_service.dart +++ b/lib/core/backup/backup_service.dart @@ -1,3 +1,5 @@ +export 'package:on_time_front/domain/entities/backup_operation.dart'; +import 'package:on_time_front/domain/entities/backup_operation.dart'; import 'package:on_time_front/core/backup/recurring_backup_data.dart'; import 'dart:convert'; import 'package:on_time_front/domain/use-cases/cancel_all_alarms_use_case.dart'; @@ -21,44 +23,21 @@ import 'package:on_time_front/domain/entities/schedule_entity.dart'; import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart'; import 'package:on_time_front/domain/entities/user_entity.dart'; -enum BackupFreshness { neverExported, noChanges, unexportedChanges } - -enum BackupExportResult { cancelled, saved, savedFreshnessUpdateFailed } - -class BackupFreshnessStatus { - const BackupFreshnessStatus({ - required this.freshness, - this.lastExportedAt, - this.reminderDue = false, - }); - - final BackupFreshness freshness; - final DateTime? lastExportedAt; - final bool reminderDue; -} - -class BackupRestorePreview { - const BackupRestorePreview({ - required this.cutoff, - required this.sourceAppVersion, - required this.sourcePlatform, - required this.scheduleCount, - required this.templateCount, - required this.defaultPreparationStepCount, - }); - - final DateTime cutoff; - final String sourceAppVersion; - final String sourcePlatform; - final int scheduleCount; - final int templateCount; - final int defaultPreparationStepCount; -} +class BackupRestoreCandidate implements BackupRestoreInput { + BackupRestoreCandidate._( + this._data, + this.preview, + this._generation, + this._revision, + ); -class BackupRestoreCandidate { - const BackupRestoreCandidate._(this._data, this.preview); + int _generation; + final int _revision; + bool _consumed = false; + Future? _running; final _BackupData _data; + @override final BackupRestorePreview preview; } @@ -90,6 +69,7 @@ class BackupService { final BackupCrypto _crypto; final BackupFileExportPort _exportPort; final LocalDataOperationGate _operationGate; + int get generation => _operationGate.generation; Future exportToUserSelectedFile( String password, @@ -150,6 +130,11 @@ class BackupService { ); final decoded = jsonDecode(utf8.decode(decrypted)); final data = _BackupData.fromJson(_asMap(decoded, 'backup')); + final previewGeneration = generation; + final revision = await _currentRevision(); + if (previewGeneration != generation) { + throw const DataOperationException(DataOperationFailure.stalePreview); + } return BackupRestoreCandidate._( data, BackupRestorePreview( @@ -161,6 +146,8 @@ class BackupService { defaultPreparationStepCount: data.defaultPreparation.preparationStepList.length, ), + previewGeneration, + revision, ); } @@ -171,14 +158,71 @@ class BackupService { ); } - Future applyRestore(BackupRestoreCandidate candidate) => - _operationGate.run(() => _applyRestore(candidate), replacesData: true); + Future _currentRevision() async => (await (_database.select( + _database.users, + )..where((t) => t.id.equals(localProfileId))).getSingle()).dataRevision; + + Future applyRestore(BackupRestoreCandidate candidate) async { + await applyRestoreWithReceipt(candidate); + } + + Future applyRestoreWithReceipt(BackupRestoreCandidate candidate) { + if (candidate._consumed) { + return Future.error( + const DataOperationException(DataOperationFailure.stalePreview), + ); + } + return candidate._running ??= _claimRestore( + candidate, + ).whenComplete(() => candidate._running = null); + } + + Future _claimRestore(BackupRestoreCandidate candidate) async { + var claimed = false; + var claimGeneration = candidate._generation; + try { + return await _operationGate.run( + () async { + claimed = true; + claimGeneration = generation; + await _applyRestore(candidate); + candidate._consumed = true; + return claimGeneration; + }, + replacesData: true, + validateReplacement: () async { + if (candidate._generation != generation || + candidate._revision != await _currentRevision()) { + throw const DataOperationException( + DataOperationFailure.stalePreview, + ); + } + }, + ); + } catch (error) { + if (!claimed) rethrow; + // The old generation and OS cleanup may already have changed, even when + // the DB transaction rejects an edit that raced the cleanup. A09 owns + // complete writer fencing and staging/runtime replacement integration. + candidate._generation = claimGeneration; + throw DataOperationException( + error is DataOperationException + ? error.failure + : DataOperationFailure.failed, + followUpPending: true, + generation: claimGeneration, + ); + } + } Future _applyRestore(BackupRestoreCandidate candidate) async { await _cancelAllAlarms.forDataReplacement(); final data = candidate._data; final profile = data.profile.valueOrNull!; await _database.transaction(() async { + if (candidate._revision != await _currentRevision()) { + throw const DataOperationException(DataOperationFailure.stalePreview); + } await _database.deleteAllDurableData(); await _database .into(_database.users) diff --git a/lib/core/database/local_data_operation_gate.dart b/lib/core/database/local_data_operation_gate.dart index 7724878c..1e5ed09e 100644 --- a/lib/core/database/local_data_operation_gate.dart +++ b/lib/core/database/local_data_operation_gate.dart @@ -18,14 +18,19 @@ final class LocalDataOperationGate extends ChangeNotifier { Future run( Future Function() action, { bool replacesData = false, + Future Function()? validateReplacement, }) async { if (_busy) throw const LocalDataOperationBusy(); if (_unavailable) throw const LocalDataUnavailable(); _busy = true; - _replacingData = replacesData; - if (replacesData) _generation++; - notifyListeners(); try { + // The exclusive claim is held before validation, but its replacement + // generation is issued only after the old preview has been accepted. + if (validateReplacement != null) await validateReplacement(); + if (_unavailable) throw const LocalDataUnavailable(); + _replacingData = replacesData; + if (replacesData) _generation++; + notifyListeners(); return await action(); } finally { _busy = false; diff --git a/lib/core/database/local_reset_protocol.dart b/lib/core/database/local_reset_protocol.dart index a630d753..66208e54 100644 --- a/lib/core/database/local_reset_protocol.dart +++ b/lib/core/database/local_reset_protocol.dart @@ -1,28 +1,9 @@ +export 'package:on_time_front/domain/entities/local_reset_result.dart' + show LocalResetResult; +import 'package:on_time_front/domain/entities/local_reset_result.dart'; import 'package:on_time_front/core/services/alarm_ownership_journal.dart'; import 'package:on_time_front/core/services/alarm_registration_cleanup.dart'; -/// A typed receipt says which facts were verified, never exposes raw errors. -final class LocalResetResult { - const LocalResetResult({ - required this.intentRecorded, - required this.completed, - this.isComplete = false, - this.waitingForPlatform = false, - this.recoveryRequired = true, - }); - final bool intentRecorded; - final Set completed; - final bool isComplete; - final bool waitingForPlatform; - final bool recoveryRequired; - bool get dataDeleted => completed.containsAll({ - ResetStep.database, - ResetStep.preferences, - ResetStep.key, - ResetStep.credentials, - }); -} - abstract interface class LocalResetActions { Future hasMarker(); Future writeMarker(); diff --git a/lib/core/services/alarm_ownership_journal.dart b/lib/core/services/alarm_ownership_journal.dart index 97b24bfb..0edb78fe 100644 --- a/lib/core/services/alarm_ownership_journal.dart +++ b/lib/core/services/alarm_ownership_journal.dart @@ -1,3 +1,6 @@ +export 'package:on_time_front/domain/entities/local_reset_result.dart' + show ResetStep; +import 'package:on_time_front/domain/entities/local_reset_result.dart'; import 'dart:convert'; import 'package:on_time_front/domain/entities/alarm_entities.dart'; @@ -46,8 +49,6 @@ abstract interface class RecoverableAlarmJournalStore enum ResetPhase { none, pending, complete } -enum ResetStep { deliveries, database, preferences, key, credentials, launch } - /// This projection contains no title, timing, payload, fingerprint or key. final class AlarmOwnership { AlarmOwnership(this.record, {required this.pending}); diff --git a/lib/data/adapters/local_data_workflow_adapters.dart b/lib/data/adapters/local_data_workflow_adapters.dart new file mode 100644 index 00000000..59438abe --- /dev/null +++ b/lib/data/adapters/local_data_workflow_adapters.dart @@ -0,0 +1,73 @@ +import 'package:injectable/injectable.dart'; +import 'package:on_time_front/core/backup/backup_service.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/database/local_data_reset_service.dart'; +import 'package:on_time_front/domain/entities/alarm_entities.dart'; +import 'package:on_time_front/domain/entities/local_reset_result.dart'; +import 'package:on_time_front/domain/ports/local_data_ports.dart'; +import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; + +@LazySingleton(as: BackupOperationsPort) +class LocalBackupAdapter implements BackupOperationsPort { + LocalBackupAdapter(this._backup); + final BackupService _backup; + @override + int get generation => _backup.generation; + Future _safe(Future Function() operation) async { + try { + return await operation(); + } on LocalDataOperationBusy { + throw const DataOperationException(DataOperationFailure.busy); + } on LocalDataUnavailable { + throw const DataOperationException(DataOperationFailure.unavailable); + } on DataOperationException { + rethrow; + } on FormatException { + throw const DataOperationException(DataOperationFailure.invalidBackup); + } catch (_) { + throw const DataOperationException(DataOperationFailure.failed); + } + } + + @override + Future freshness() => _safe(_backup.getFreshness); + @override + Future export(String password) => + _safe(() => _backup.exportToUserSelectedFile(password)); + @override + Future preview(String password) => + _safe(() => _backup.selectAndPreviewRestore(password)); + @override + Future apply(BackupRestoreInput input) => _safe( + () => _backup.applyRestoreWithReceipt(input as BackupRestoreCandidate), + ); +} + +@LazySingleton(as: RestoreDeliveryPort) +class LocalRestoreDeliveryAdapter implements RestoreDeliveryPort { + LocalRestoreDeliveryAdapter(this._reconcile); + final ReconcileAlarmsUseCase _reconcile; + @override + Future reconcile() async { + final result = await _reconcile(); + return (result.status == AlarmReconciliationStatus.armed || + result.status == AlarmReconciliationStatus.disabled) && + result.failures.isEmpty; + } +} + +@LazySingleton(as: LocalResetPort) +class LocalResetAdapter implements LocalResetPort { + LocalResetAdapter(this._service); + final LocalDataResetService _service; + @override + Future reset() => _service.reset(); +} + +/// The same domain workflow can be used before DI without opening AppDatabase. +class RecoveryResetAdapter implements LocalResetPort { + RecoveryResetAdapter(this._operation); + final Future Function() _operation; + @override + Future reset() => _operation(); +} diff --git a/lib/domain/entities/backup_operation.dart b/lib/domain/entities/backup_operation.dart new file mode 100644 index 00000000..980546a5 --- /dev/null +++ b/lib/domain/entities/backup_operation.dart @@ -0,0 +1,69 @@ +enum BackupFreshness { neverExported, noChanges, unexportedChanges } + +enum BackupExportResult { cancelled, saved, savedFreshnessUpdateFailed } + +class BackupFreshnessStatus { + const BackupFreshnessStatus({ + required this.freshness, + this.lastExportedAt, + this.reminderDue = false, + }); + + final BackupFreshness freshness; + final DateTime? lastExportedAt; + final bool reminderDue; +} + +class BackupRestorePreview { + const BackupRestorePreview({ + required this.cutoff, + required this.sourceAppVersion, + required this.sourcePlatform, + required this.scheduleCount, + required this.templateCount, + required this.defaultPreparationStepCount, + }); + + final DateTime cutoff; + final String sourceAppVersion; + final String sourcePlatform; + final int scheduleCount; + final int templateCount; + final int defaultPreparationStepCount; +} + +abstract class BackupRestoreInput { + BackupRestorePreview get preview; +} + +enum DataOperationFailure { + busy, + unavailable, + invalidBackup, + stalePreview, + failed, +} + +class DataOperationException implements Exception { + const DataOperationException( + this.failure, { + this.followUpPending = false, + this.generation, + }); + final DataOperationFailure failure; + final int? generation; + final bool followUpPending; +} + +class BackupRestoreReceipt { + const BackupRestoreReceipt({ + required this.committed, + required this.generation, + this.followUpPending = false, + this.failure, + }); + final bool committed; + final int generation; + final bool followUpPending; + final DataOperationFailure? failure; +} diff --git a/lib/domain/entities/local_reset_result.dart b/lib/domain/entities/local_reset_result.dart new file mode 100644 index 00000000..cc427dfb --- /dev/null +++ b/lib/domain/entities/local_reset_result.dart @@ -0,0 +1,23 @@ +enum ResetStep { deliveries, database, preferences, key, credentials, launch } + +/// A typed receipt says which facts were verified, never exposes raw errors. +final class LocalResetResult { + const LocalResetResult({ + required this.intentRecorded, + required this.completed, + this.isComplete = false, + this.waitingForPlatform = false, + this.recoveryRequired = true, + }); + final bool intentRecorded; + final Set completed; + final bool isComplete; + final bool waitingForPlatform; + final bool recoveryRequired; + bool get dataDeleted => completed.containsAll({ + ResetStep.database, + ResetStep.preferences, + ResetStep.key, + ResetStep.credentials, + }); +} diff --git a/lib/domain/ports/local_data_ports.dart b/lib/domain/ports/local_data_ports.dart new file mode 100644 index 00000000..a0d0cfde --- /dev/null +++ b/lib/domain/ports/local_data_ports.dart @@ -0,0 +1,18 @@ +import 'package:on_time_front/domain/entities/backup_operation.dart'; +import 'package:on_time_front/domain/entities/local_reset_result.dart'; + +abstract interface class BackupOperationsPort { + int get generation; + Future freshness(); + Future export(String password); + Future preview(String password); + Future apply(BackupRestoreInput input); +} + +abstract interface class RestoreDeliveryPort { + Future reconcile(); +} + +abstract interface class LocalResetPort { + Future reset(); +} diff --git a/lib/domain/use-cases/local_data_workflows.dart b/lib/domain/use-cases/local_data_workflows.dart new file mode 100644 index 00000000..3c3cff1f --- /dev/null +++ b/lib/domain/use-cases/local_data_workflows.dart @@ -0,0 +1,109 @@ +import 'package:injectable/injectable.dart'; +import 'package:on_time_front/domain/entities/backup_operation.dart'; +import 'package:on_time_front/domain/entities/local_reset_result.dart'; +import 'package:on_time_front/domain/ports/local_data_ports.dart'; + +@lazySingleton +class BackupWorkflow { + BackupWorkflow(this._backup, this._delivery); + final BackupOperationsPort _backup; + final RestoreDeliveryPort _delivery; + final _pending = Expando>(); + final _completed = Expando(); + final _inputs = Expando>(); + final _retries = Expando>(); + int get generation => _backup.generation; + + Future freshness() => _backup.freshness(); + Future export(String password) => + _backup.export(password); + Future preview(String password) => + _backup.preview(password); + + Future restore(BackupRestoreInput input) { + final running = _pending[input]; + if (running != null) return running; + final completed = _completed[input]; + if (completed != null) return Future.value(completed); + return _pending[input] ??= _restore( + input, + ).whenComplete(() => _pending[input] = null); + } + + Future _restore(BackupRestoreInput input) async { + final startedGeneration = generation; + final int committedGeneration; + try { + committedGeneration = await _backup.apply(input); + } on DataOperationException catch (error) { + return BackupRestoreReceipt( + committed: false, + generation: error.generation ?? startedGeneration, + failure: error.failure, + followUpPending: error.followUpPending, + ); + } catch (_) { + return BackupRestoreReceipt( + committed: false, + generation: startedGeneration, + failure: DataOperationFailure.failed, + ); + } + + // Record commit before waiting for external work. A thrown delivery result + // can never cause the caller to replace durable data again. + final pending = BackupRestoreReceipt( + committed: true, + generation: committedGeneration, + followUpPending: true, + ); + _completed[input] = pending; + _inputs[pending] = WeakReference(input); + final result = await _followUp(pending); + _completed[input] = result; + _inputs[result] = WeakReference(input); + return result; + } + + Future retryFollowUp(BackupRestoreReceipt receipt) { + if (!receipt.followUpPending) return Future.value(receipt); + return _retries[receipt] ??= _followUp( + receipt, + ).whenComplete(() => _retries[receipt] = null); + } + + Future _followUp(BackupRestoreReceipt receipt) async { + if (receipt.generation != generation) { + return BackupRestoreReceipt( + committed: receipt.committed, + generation: receipt.generation, + followUpPending: true, + failure: DataOperationFailure.stalePreview, + ); + } + var complete = false; + try { + complete = await _delivery.reconcile(); + } catch (_) { + /* Safe partial receipt. */ + } + final result = BackupRestoreReceipt( + committed: receipt.committed, + generation: receipt.generation, + followUpPending: !complete || receipt.generation != generation, + ); + final input = _inputs[receipt]?.target; + if (input != null) { + _completed[input] = result; + _inputs[result] = WeakReference(input); + } + return result; + } +} + +@lazySingleton +class LocalResetWorkflow { + LocalResetWorkflow(this._reset); + final LocalResetPort _reset; + Future call() => _reset.reset(); +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a1982d7a..41a0081e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -626,5 +626,67 @@ "startupLoadingBody": "Checking the data stored on this device.", "startupWaitingBody": "Waiting for the device to respond. Please wait until the check finishes.", "preparationStartFailed": "Preparation could not be started. Try again.", - "preparationStartPartial": "Preparation has started. Some progress storage or alarm cleanup is incomplete." + "preparationStartPartial": "Preparation has started. Some progress storage or alarm cleanup is incomplete.", + "dataTitle": "My data", + "dataBackupStatus": "Backup status", + "dataReminder": "Some changes have not been backed up for at least 30 days.", + "dataExport": "Export encrypted backup", + "dataExportDescription": "Save only to the file location you choose.", + "dataRestore": "Restore from backup", + "dataRestoreDescription": "Review the backup before replacing all current data.", + "dataReset": "Reset local data", + "dataResetDescription": "Delete all OnTime data and alarms on this device.", + "dataChecking": "Checking", + "dataNeverExported": "No backup has been exported yet.", + "dataNoChanges": "No changes since the last backup.", + "dataUnexportedChanges": "Some changes have not been backed up.", + "dataFreshnessFailed": "Backup status could not be checked. Try again.", + "dataRetry": "Try again", + "dataRetryDelivery": "Retry notification processing only", + "dataRestorePartial": "Data was restored, but notification processing is incomplete. You can retry notification processing only.", + "dataUncommittedCleanup": "Data was not restored. Notification cleanup already began; retry notification processing for the current data.", + "dataExportSaved": "Encrypted backup saved.", + "dataExportMetadataFailed": "The file was saved, but backup status could not be updated. Retry checking backup status.", + "dataRestoreComplete": "Backup restored.", + "dataDeliveryUpdated": "Notification processing completed for the current data.", + "dataBusy": "Another data operation is in progress. Try again after it finishes.", + "dataUnavailable": "Local data is unavailable. Reopen the app.", + "dataStalePreview": "Data changed after the preview. Select and review the backup again.", + "dataInvalidBackup": "The operation could not be completed. Check the backup file and password.", + "dataOperationFailed": "The operation could not be completed. Try again.", + "dataRestorePreviewTitle": "Review restore", + "dataRestoreAction": "Restore", + "dataCancel": "Cancel", + "dataContinue": "Continue", + "dataCreatePassword": "Create backup password", + "dataEnterPassword": "Enter backup password", + "dataPassword": "Backup password", + "dataPasswordHelp": "15–128 characters. Case and spaces are preserved.", + "dataConfirmPassword": "Confirm backup password", + "dataPasswordMismatch": "Passwords do not match.", + "dataPasswordInvalid": "Enter a backup password of 15–128 characters.", + "dataRestorePreview": "Backup cutoff: {cutoff}\nApp version: {version}\nSource platform: {platform}\nSchedules: {schedules}\nPreparation templates: {templates}\nDefault preparation steps: {steps}\n\nAll current local data will be replaced.", + "@dataRestorePreview": { + "placeholders": { + "cutoff": { + "type": "String" + }, + "version": { + "type": "String" + }, + "platform": { + "type": "String" + }, + "schedules": { + "type": "int" + }, + "templates": { + "type": "int" + }, + "steps": { + "type": "int" + } + } + }, + "dataResetFailed": "Local data could not be reset. Try again." } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 5ea27741..48c2a769 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -223,5 +223,67 @@ "startupLoadingBody": "기기에 저장된 데이터를 확인하고 있습니다.", "startupWaitingBody": "기기의 응답을 기다리고 있습니다. 확인이 끝날 때까지 잠시 기다려 주세요.", "preparationStartFailed": "준비 시작을 저장하지 못했어요. 다시 시도해주세요.", - "preparationStartPartial": "준비는 시작했지만 진행 저장 또는 알림 정리가 일부 완료되지 않았어요." + "preparationStartPartial": "준비는 시작했지만 진행 저장 또는 알림 정리가 일부 완료되지 않았어요.", + "dataTitle": "내 데이터", + "dataBackupStatus": "백업 상태", + "dataReminder": "30일 이상 백업되지 않은 변경 사항이 있습니다.", + "dataExport": "암호화 백업 내보내기", + "dataExportDescription": "선택한 파일 위치에만 저장합니다.", + "dataRestore": "백업에서 복원", + "dataRestoreDescription": "미리 확인한 뒤 현재 데이터를 완전히 교체합니다.", + "dataReset": "로컬 데이터 초기화", + "dataResetDescription": "이 기기의 OnTime 데이터와 알람을 모두 삭제합니다.", + "dataChecking": "확인 중", + "dataNeverExported": "아직 내보낸 백업이 없습니다.", + "dataNoChanges": "마지막 백업 이후 변경 사항이 없습니다.", + "dataUnexportedChanges": "백업되지 않은 변경 사항이 있습니다.", + "dataFreshnessFailed": "백업 상태를 확인하지 못했습니다. 다시 시도해 주세요.", + "dataRetry": "다시 시도", + "dataRetryDelivery": "알림 처리만 다시 시도", + "dataRestorePartial": "데이터는 복원됐지만 알림 처리가 완료되지 않았습니다. 알림 처리만 다시 시도할 수 있습니다.", + "dataUncommittedCleanup": "데이터는 복원되지 않았습니다. 이미 시작된 알림 정리를 마무리하려면 현재 데이터의 알림 처리를 다시 시도해 주세요.", + "dataExportSaved": "암호화 백업을 저장했습니다.", + "dataExportMetadataFailed": "파일은 저장됐지만 백업 상태를 갱신하지 못했습니다. 백업 상태 확인만 다시 시도해 주세요.", + "dataRestoreComplete": "백업을 복원했습니다.", + "dataDeliveryUpdated": "현재 데이터의 알림 처리를 완료했습니다.", + "dataBusy": "다른 데이터 작업이 진행 중입니다. 완료 후 다시 시도해 주세요.", + "dataUnavailable": "로컬 데이터를 사용할 수 없습니다. 앱을 다시 열어 주세요.", + "dataStalePreview": "미리보기 이후 데이터가 변경됐습니다. 백업을 다시 선택하고 확인해 주세요.", + "dataInvalidBackup": "작업을 완료하지 못했습니다. 백업 파일과 비밀번호를 확인해 주세요.", + "dataOperationFailed": "작업을 완료하지 못했습니다. 다시 시도해 주세요.", + "dataRestorePreviewTitle": "복원 내용 확인", + "dataRestoreAction": "복원", + "dataCancel": "취소", + "dataContinue": "계속", + "dataCreatePassword": "백업 비밀번호 만들기", + "dataEnterPassword": "백업 비밀번호 입력", + "dataPassword": "백업 비밀번호", + "dataPasswordHelp": "15~128자, 대소문자와 공백을 그대로 구분합니다.", + "dataConfirmPassword": "백업 비밀번호 확인", + "dataPasswordMismatch": "비밀번호가 서로 다릅니다.", + "dataPasswordInvalid": "백업 비밀번호는 15~128자로 입력해주세요.", + "dataRestorePreview": "백업 시점: {cutoff}\n앱 버전: {version}\n원본 플랫폼: {platform}\n일정 {schedules}개\n준비 템플릿 {templates}개\n기본 준비 단계 {steps}개\n\n현재 로컬 데이터는 모두 교체됩니다.", + "@dataRestorePreview": { + "placeholders": { + "cutoff": { + "type": "String" + }, + "version": { + "type": "String" + }, + "platform": { + "type": "String" + }, + "schedules": { + "type": "int" + }, + "templates": { + "type": "int" + }, + "steps": { + "type": "int" + } + } + }, + "dataResetFailed": "초기화하지 못했습니다. 다시 시도해 주세요." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 17a955e0..3039f29d 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1075,6 +1075,253 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Preparation has started. Some progress storage or alarm cleanup is incomplete.'** String get preparationStartPartial; + + /// No description provided for @dataTitle. + /// + /// In en, this message translates to: + /// **'My data'** + String get dataTitle; + + /// No description provided for @dataBackupStatus. + /// + /// In en, this message translates to: + /// **'Backup status'** + String get dataBackupStatus; + + /// No description provided for @dataReminder. + /// + /// In en, this message translates to: + /// **'Some changes have not been backed up for at least 30 days.'** + String get dataReminder; + + /// No description provided for @dataExport. + /// + /// In en, this message translates to: + /// **'Export encrypted backup'** + String get dataExport; + + /// No description provided for @dataExportDescription. + /// + /// In en, this message translates to: + /// **'Save only to the file location you choose.'** + String get dataExportDescription; + + /// No description provided for @dataRestore. + /// + /// In en, this message translates to: + /// **'Restore from backup'** + String get dataRestore; + + /// No description provided for @dataRestoreDescription. + /// + /// In en, this message translates to: + /// **'Review the backup before replacing all current data.'** + String get dataRestoreDescription; + + /// No description provided for @dataReset. + /// + /// In en, this message translates to: + /// **'Reset local data'** + String get dataReset; + + /// No description provided for @dataResetDescription. + /// + /// In en, this message translates to: + /// **'Delete all OnTime data and alarms on this device.'** + String get dataResetDescription; + + /// No description provided for @dataChecking. + /// + /// In en, this message translates to: + /// **'Checking'** + String get dataChecking; + + /// No description provided for @dataNeverExported. + /// + /// In en, this message translates to: + /// **'No backup has been exported yet.'** + String get dataNeverExported; + + /// No description provided for @dataNoChanges. + /// + /// In en, this message translates to: + /// **'No changes since the last backup.'** + String get dataNoChanges; + + /// No description provided for @dataUnexportedChanges. + /// + /// In en, this message translates to: + /// **'Some changes have not been backed up.'** + String get dataUnexportedChanges; + + /// No description provided for @dataFreshnessFailed. + /// + /// In en, this message translates to: + /// **'Backup status could not be checked. Try again.'** + String get dataFreshnessFailed; + + /// No description provided for @dataRetry. + /// + /// In en, this message translates to: + /// **'Try again'** + String get dataRetry; + + /// No description provided for @dataRetryDelivery. + /// + /// In en, this message translates to: + /// **'Retry notification processing only'** + String get dataRetryDelivery; + + /// No description provided for @dataRestorePartial. + /// + /// In en, this message translates to: + /// **'Data was restored, but notification processing is incomplete. You can retry notification processing only.'** + String get dataRestorePartial; + + /// No description provided for @dataUncommittedCleanup. + /// + /// In en, this message translates to: + /// **'Data was not restored. Notification cleanup already began; retry notification processing for the current data.'** + String get dataUncommittedCleanup; + + /// No description provided for @dataExportSaved. + /// + /// In en, this message translates to: + /// **'Encrypted backup saved.'** + String get dataExportSaved; + + /// No description provided for @dataExportMetadataFailed. + /// + /// In en, this message translates to: + /// **'The file was saved, but backup status could not be updated. Retry checking backup status.'** + String get dataExportMetadataFailed; + + /// No description provided for @dataRestoreComplete. + /// + /// In en, this message translates to: + /// **'Backup restored.'** + String get dataRestoreComplete; + + /// No description provided for @dataDeliveryUpdated. + /// + /// In en, this message translates to: + /// **'Notification processing completed for the current data.'** + String get dataDeliveryUpdated; + + /// No description provided for @dataBusy. + /// + /// In en, this message translates to: + /// **'Another data operation is in progress. Try again after it finishes.'** + String get dataBusy; + + /// No description provided for @dataUnavailable. + /// + /// In en, this message translates to: + /// **'Local data is unavailable. Reopen the app.'** + String get dataUnavailable; + + /// No description provided for @dataStalePreview. + /// + /// In en, this message translates to: + /// **'Data changed after the preview. Select and review the backup again.'** + String get dataStalePreview; + + /// No description provided for @dataInvalidBackup. + /// + /// In en, this message translates to: + /// **'The operation could not be completed. Check the backup file and password.'** + String get dataInvalidBackup; + + /// No description provided for @dataOperationFailed. + /// + /// In en, this message translates to: + /// **'The operation could not be completed. Try again.'** + String get dataOperationFailed; + + /// No description provided for @dataRestorePreviewTitle. + /// + /// In en, this message translates to: + /// **'Review restore'** + String get dataRestorePreviewTitle; + + /// No description provided for @dataRestoreAction. + /// + /// In en, this message translates to: + /// **'Restore'** + String get dataRestoreAction; + + /// No description provided for @dataCancel. + /// + /// In en, this message translates to: + /// **'Cancel'** + String get dataCancel; + + /// No description provided for @dataContinue. + /// + /// In en, this message translates to: + /// **'Continue'** + String get dataContinue; + + /// No description provided for @dataCreatePassword. + /// + /// In en, this message translates to: + /// **'Create backup password'** + String get dataCreatePassword; + + /// No description provided for @dataEnterPassword. + /// + /// In en, this message translates to: + /// **'Enter backup password'** + String get dataEnterPassword; + + /// No description provided for @dataPassword. + /// + /// In en, this message translates to: + /// **'Backup password'** + String get dataPassword; + + /// No description provided for @dataPasswordHelp. + /// + /// In en, this message translates to: + /// **'15–128 characters. Case and spaces are preserved.'** + String get dataPasswordHelp; + + /// No description provided for @dataConfirmPassword. + /// + /// In en, this message translates to: + /// **'Confirm backup password'** + String get dataConfirmPassword; + + /// No description provided for @dataPasswordMismatch. + /// + /// In en, this message translates to: + /// **'Passwords do not match.'** + String get dataPasswordMismatch; + + /// No description provided for @dataPasswordInvalid. + /// + /// In en, this message translates to: + /// **'Enter a backup password of 15–128 characters.'** + String get dataPasswordInvalid; + + /// No description provided for @dataRestorePreview. + /// + /// In en, this message translates to: + /// **'Backup cutoff: {cutoff}\nApp version: {version}\nSource platform: {platform}\nSchedules: {schedules}\nPreparation templates: {templates}\nDefault preparation steps: {steps}\n\nAll current local data will be replaced.'** + String dataRestorePreview( + String cutoff, + String version, + String platform, + int schedules, + int templates, + int steps, + ); + + /// No description provided for @dataResetFailed. + /// + /// In en, this message translates to: + /// **'Local data could not be reset. Try again.'** + String get dataResetFailed; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 465a1385..760fe79e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -577,4 +577,148 @@ class AppLocalizationsEn extends AppLocalizations { @override String get preparationStartPartial => 'Preparation has started. Some progress storage or alarm cleanup is incomplete.'; + + @override + String get dataTitle => 'My data'; + + @override + String get dataBackupStatus => 'Backup status'; + + @override + String get dataReminder => + 'Some changes have not been backed up for at least 30 days.'; + + @override + String get dataExport => 'Export encrypted backup'; + + @override + String get dataExportDescription => + 'Save only to the file location you choose.'; + + @override + String get dataRestore => 'Restore from backup'; + + @override + String get dataRestoreDescription => + 'Review the backup before replacing all current data.'; + + @override + String get dataReset => 'Reset local data'; + + @override + String get dataResetDescription => + 'Delete all OnTime data and alarms on this device.'; + + @override + String get dataChecking => 'Checking'; + + @override + String get dataNeverExported => 'No backup has been exported yet.'; + + @override + String get dataNoChanges => 'No changes since the last backup.'; + + @override + String get dataUnexportedChanges => 'Some changes have not been backed up.'; + + @override + String get dataFreshnessFailed => + 'Backup status could not be checked. Try again.'; + + @override + String get dataRetry => 'Try again'; + + @override + String get dataRetryDelivery => 'Retry notification processing only'; + + @override + String get dataRestorePartial => + 'Data was restored, but notification processing is incomplete. You can retry notification processing only.'; + + @override + String get dataUncommittedCleanup => + 'Data was not restored. Notification cleanup already began; retry notification processing for the current data.'; + + @override + String get dataExportSaved => 'Encrypted backup saved.'; + + @override + String get dataExportMetadataFailed => + 'The file was saved, but backup status could not be updated. Retry checking backup status.'; + + @override + String get dataRestoreComplete => 'Backup restored.'; + + @override + String get dataDeliveryUpdated => + 'Notification processing completed for the current data.'; + + @override + String get dataBusy => + 'Another data operation is in progress. Try again after it finishes.'; + + @override + String get dataUnavailable => 'Local data is unavailable. Reopen the app.'; + + @override + String get dataStalePreview => + 'Data changed after the preview. Select and review the backup again.'; + + @override + String get dataInvalidBackup => + 'The operation could not be completed. Check the backup file and password.'; + + @override + String get dataOperationFailed => + 'The operation could not be completed. Try again.'; + + @override + String get dataRestorePreviewTitle => 'Review restore'; + + @override + String get dataRestoreAction => 'Restore'; + + @override + String get dataCancel => 'Cancel'; + + @override + String get dataContinue => 'Continue'; + + @override + String get dataCreatePassword => 'Create backup password'; + + @override + String get dataEnterPassword => 'Enter backup password'; + + @override + String get dataPassword => 'Backup password'; + + @override + String get dataPasswordHelp => + '15–128 characters. Case and spaces are preserved.'; + + @override + String get dataConfirmPassword => 'Confirm backup password'; + + @override + String get dataPasswordMismatch => 'Passwords do not match.'; + + @override + String get dataPasswordInvalid => + 'Enter a backup password of 15–128 characters.'; + + @override + String dataRestorePreview( + String cutoff, + String version, + String platform, + int schedules, + int templates, + int steps, + ) { + return 'Backup cutoff: $cutoff\nApp version: $version\nSource platform: $platform\nSchedules: $schedules\nPreparation templates: $templates\nDefault preparation steps: $steps\n\nAll current local data will be replaced.'; + } + + @override + String get dataResetFailed => 'Local data could not be reset. Try again.'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 2eb292e9..653c038c 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -536,4 +536,136 @@ class AppLocalizationsKo extends AppLocalizations { @override String get preparationStartPartial => '준비는 시작했지만 진행 저장 또는 알림 정리가 일부 완료되지 않았어요.'; + + @override + String get dataTitle => '내 데이터'; + + @override + String get dataBackupStatus => '백업 상태'; + + @override + String get dataReminder => '30일 이상 백업되지 않은 변경 사항이 있습니다.'; + + @override + String get dataExport => '암호화 백업 내보내기'; + + @override + String get dataExportDescription => '선택한 파일 위치에만 저장합니다.'; + + @override + String get dataRestore => '백업에서 복원'; + + @override + String get dataRestoreDescription => '미리 확인한 뒤 현재 데이터를 완전히 교체합니다.'; + + @override + String get dataReset => '로컬 데이터 초기화'; + + @override + String get dataResetDescription => '이 기기의 OnTime 데이터와 알람을 모두 삭제합니다.'; + + @override + String get dataChecking => '확인 중'; + + @override + String get dataNeverExported => '아직 내보낸 백업이 없습니다.'; + + @override + String get dataNoChanges => '마지막 백업 이후 변경 사항이 없습니다.'; + + @override + String get dataUnexportedChanges => '백업되지 않은 변경 사항이 있습니다.'; + + @override + String get dataFreshnessFailed => '백업 상태를 확인하지 못했습니다. 다시 시도해 주세요.'; + + @override + String get dataRetry => '다시 시도'; + + @override + String get dataRetryDelivery => '알림 처리만 다시 시도'; + + @override + String get dataRestorePartial => + '데이터는 복원됐지만 알림 처리가 완료되지 않았습니다. 알림 처리만 다시 시도할 수 있습니다.'; + + @override + String get dataUncommittedCleanup => + '데이터는 복원되지 않았습니다. 이미 시작된 알림 정리를 마무리하려면 현재 데이터의 알림 처리를 다시 시도해 주세요.'; + + @override + String get dataExportSaved => '암호화 백업을 저장했습니다.'; + + @override + String get dataExportMetadataFailed => + '파일은 저장됐지만 백업 상태를 갱신하지 못했습니다. 백업 상태 확인만 다시 시도해 주세요.'; + + @override + String get dataRestoreComplete => '백업을 복원했습니다.'; + + @override + String get dataDeliveryUpdated => '현재 데이터의 알림 처리를 완료했습니다.'; + + @override + String get dataBusy => '다른 데이터 작업이 진행 중입니다. 완료 후 다시 시도해 주세요.'; + + @override + String get dataUnavailable => '로컬 데이터를 사용할 수 없습니다. 앱을 다시 열어 주세요.'; + + @override + String get dataStalePreview => '미리보기 이후 데이터가 변경됐습니다. 백업을 다시 선택하고 확인해 주세요.'; + + @override + String get dataInvalidBackup => '작업을 완료하지 못했습니다. 백업 파일과 비밀번호를 확인해 주세요.'; + + @override + String get dataOperationFailed => '작업을 완료하지 못했습니다. 다시 시도해 주세요.'; + + @override + String get dataRestorePreviewTitle => '복원 내용 확인'; + + @override + String get dataRestoreAction => '복원'; + + @override + String get dataCancel => '취소'; + + @override + String get dataContinue => '계속'; + + @override + String get dataCreatePassword => '백업 비밀번호 만들기'; + + @override + String get dataEnterPassword => '백업 비밀번호 입력'; + + @override + String get dataPassword => '백업 비밀번호'; + + @override + String get dataPasswordHelp => '15~128자, 대소문자와 공백을 그대로 구분합니다.'; + + @override + String get dataConfirmPassword => '백업 비밀번호 확인'; + + @override + String get dataPasswordMismatch => '비밀번호가 서로 다릅니다.'; + + @override + String get dataPasswordInvalid => '백업 비밀번호는 15~128자로 입력해주세요.'; + + @override + String dataRestorePreview( + String cutoff, + String version, + String platform, + int schedules, + int templates, + int steps, + ) { + return '백업 시점: $cutoff\n앱 버전: $version\n원본 플랫폼: $platform\n일정 $schedules개\n준비 템플릿 $templates개\n기본 준비 단계 $steps개\n\n현재 로컬 데이터는 모두 교체됩니다.'; + } + + @override + String get dataResetFailed => '초기화하지 못했습니다. 다시 시도해 주세요.'; } diff --git a/lib/main.dart b/lib/main.dart index 2886dc72..27c36f95 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,8 @@ import 'package:intl/date_symbol_data_local.dart'; import 'package:on_time_front/core/di/di_setup.dart'; import 'package:on_time_front/core/database/local_data_lifecycle.dart'; import 'package:on_time_front/core/database/local_data_operation_gate.dart'; -import 'package:on_time_front/core/database/local_data_reset_service.dart'; +import 'package:on_time_front/domain/use-cases/local_data_workflows.dart'; +import 'package:on_time_front/data/adapters/local_data_workflow_adapters.dart'; import 'package:on_time_front/core/logging/app_logger.dart'; import 'package:on_time_front/core/services/device_info_service/shared.dart'; import 'package:on_time_front/core/services/notification_service.dart'; @@ -30,7 +31,9 @@ void main() async { shouldCleanup: (error) => error is! LocalResetRecoveryRequired, ); }, - retryReset: LocalDataLifecycle.resumeReset, + retryReset: LocalResetWorkflow( + RecoveryResetAdapter(LocalDataLifecycle.resumeReset), + ).call, ready: () { configureDependencies(); NotificationService.instance.configureDelegate( @@ -41,7 +44,7 @@ void main() async { ); return ResetAwareApp( gate: LocalDataOperationGate.shared, - reset: () => getIt().reset(), + reset: () => getIt()(), child: const App(), ); }, diff --git a/lib/presentation/my_page/my_data_screen.dart b/lib/presentation/my_page/my_data_screen.dart index eccf847c..43c62430 100644 --- a/lib/presentation/my_page/my_data_screen.dart +++ b/lib/presentation/my_page/my_data_screen.dart @@ -1,19 +1,26 @@ -import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/domain/entities/local_reset_result.dart'; +import 'package:on_time_front/domain/entities/backup_operation.dart'; +import 'package:on_time_front/domain/use-cases/local_data_workflows.dart'; +import 'package:on_time_front/l10n/app_localizations.dart'; import 'package:on_time_front/presentation/startup/screens/local_reset_progress_screen.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:on_time_front/presentation/recurring/recurrence_components.dart'; import 'package:on_time_front/presentation/startup/screens/local_data_recovery_screen.dart'; import 'package:flutter/material.dart'; import 'package:on_time_front/core/backup/backup_password.dart'; -import 'package:on_time_front/core/backup/backup_service.dart'; -import 'package:on_time_front/core/database/local_data_reset_service.dart'; import 'package:on_time_front/core/di/di_setup.dart'; -import 'package:on_time_front/domain/use-cases/reconcile_alarms_use_case.dart'; class MyDataScreen extends StatefulWidget { - const MyDataScreen({super.key, this.initialAction}); + const MyDataScreen({ + super.key, + this.initialAction, + this.workflow, + this.resetWorkflow, + }); final String? initialAction; + final BackupWorkflow? workflow; + final LocalResetWorkflow? resetWorkflow; @override State createState() => _MyDataScreenState(); @@ -21,6 +28,19 @@ class MyDataScreen extends StatefulWidget { class _MyDataScreenState extends State { bool _busy = false; + bool _working = false; + bool _freshnessFailed = false; + int _freshnessRequest = 0; + BackupRestoreReceipt? _restoreReceipt; + BackupWorkflow get _workflow => widget.workflow ?? getIt(); + LocalResetWorkflow get _reset => + widget.resetWorkflow ?? getIt(); + AppLocalizations get l10n => + AppLocalizations.of(context) ?? + lookupAppLocalizations(const Locale('ko')); + bool get _routeCurrent => + mounted && (ModalRoute.of(context)?.isCurrent ?? true); + LocalResetResult? _resetResult; BackupFreshnessStatus? _freshness; @@ -37,15 +57,32 @@ class _MyDataScreenState extends State { case 'restore': _restore(); case 'reset': - _reset(); + _resetData(); } }); } } Future _loadFreshness() async { - final value = await getIt().getFreshness(); - if (mounted) setState(() => _freshness = value); + final request = ++_freshnessRequest; + final generation = _workflow.generation; + try { + final value = await _workflow.freshness(); + if (mounted && + request == _freshnessRequest && + generation == _workflow.generation) { + setState(() { + _freshness = value; + _freshnessFailed = false; + }); + } + } catch (_) { + if (mounted && + request == _freshnessRequest && + generation == _workflow.generation) { + setState(() => _freshnessFailed = true); + } + } } @override @@ -54,12 +91,12 @@ class _MyDataScreenState extends State { if (resetResult != null) { return LocalResetProgressScreen( initialResult: resetResult, - operation: getIt().reset, + operation: _reset.call, ); } final freshness = _freshness; return Scaffold( - appBar: AppBar(title: const Text('내 데이터')), + appBar: AppBar(title: Text(l10n.dataTitle)), body: ListView( padding: const EdgeInsets.all(16), children: [ @@ -69,13 +106,25 @@ class _MyDataScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('백업 상태', style: Theme.of(context).textTheme.titleMedium), + Text( + l10n.dataBackupStatus, + style: Theme.of(context).textTheme.titleMedium, + ), const SizedBox(height: 8), - Text(_freshnessLabel(freshness)), + Text( + _freshnessFailed + ? l10n.dataFreshnessFailed + : _freshnessLabel(freshness), + ), + if (_freshnessFailed) + TextButton( + onPressed: _busy ? null : _loadFreshness, + child: Text(l10n.dataRetry), + ), if (freshness?.reminderDue == true) ...[ const SizedBox(height: 8), Text( - '30일 이상 백업되지 않은 변경 사항이 있습니다.', + l10n.dataReminder, style: TextStyle( color: Theme.of(context).colorScheme.error, ), @@ -86,18 +135,38 @@ class _MyDataScreenState extends State { ), ), const SizedBox(height: 12), + if (_restoreReceipt?.followUpPending == true) + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _restoreReceipt!.committed + ? l10n.dataRestorePartial + : l10n.dataUncommittedCleanup, + ), + TextButton( + onPressed: _busy ? null : _retryRestoreDelivery, + child: Text(l10n.dataRetryDelivery), + ), + ], + ), + ), + ), ListTile( enabled: !_busy, leading: const Icon(Icons.lock_outline), - title: const Text('암호화 백업 내보내기'), - subtitle: const Text('선택한 파일 위치에만 저장합니다.'), + title: Text(l10n.dataExport), + subtitle: Text(l10n.dataExportDescription), onTap: _export, ), ListTile( enabled: !_busy, leading: const Icon(Icons.restore), - title: const Text('백업에서 복원'), - subtitle: const Text('미리 확인한 뒤 현재 데이터를 완전히 교체합니다.'), + title: Text(l10n.dataRestore), + subtitle: Text(l10n.dataRestoreDescription), onTap: _restore, ), const Divider(height: 32), @@ -108,13 +177,13 @@ class _MyDataScreenState extends State { color: Theme.of(context).colorScheme.error, ), title: Text( - '로컬 데이터 초기화', + l10n.dataReset, style: TextStyle(color: Theme.of(context).colorScheme.error), ), - subtitle: const Text('이 기기의 OnTime 데이터와 알람을 모두 삭제합니다.'), - onTap: _reset, + subtitle: Text(l10n.dataResetDescription), + onTap: _resetData, ), - if (_busy) + if (_working) const Padding( padding: EdgeInsets.only(top: 24), child: Center(child: CircularProgressIndicator()), @@ -125,92 +194,184 @@ class _MyDataScreenState extends State { } String _freshnessLabel(BackupFreshnessStatus? status) { - if (status == null) return '확인 중'; + if (status == null) return l10n.dataChecking; return switch (status.freshness) { - BackupFreshness.neverExported => '아직 내보낸 백업이 없습니다.', - BackupFreshness.noChanges => '마지막 백업 이후 변경 사항이 없습니다.', - BackupFreshness.unexportedChanges => '백업되지 않은 변경 사항이 있습니다.', + BackupFreshness.neverExported => l10n.dataNeverExported, + BackupFreshness.noChanges => l10n.dataNoChanges, + BackupFreshness.unexportedChanges => l10n.dataUnexportedChanges, }; } Future _export() async { - final password = await _askPassword(confirm: true); - if (password == null) return; - await _run(() async { - final saved = await getIt().exportToUserSelectedFile( - password, + if (_busy) return; + setState(() => _busy = true); + final operationGeneration = _workflow.generation; + try { + final password = await _askPassword(confirm: true); + if (password == null || + !_routeCurrent || + operationGeneration != _workflow.generation) { + return; + } + final generation = _workflow.generation; + setState(() => _working = true); + final saved = await _workflow.export(password); + if (!_routeCurrent || + generation != _workflow.generation || + saved == BackupExportResult.cancelled) { + return; + } + _message( + saved == BackupExportResult.saved + ? l10n.dataExportSaved + : l10n.dataExportMetadataFailed, ); - if (!mounted || saved == BackupExportResult.cancelled) return; - final message = saved == BackupExportResult.saved - ? '암호화 백업을 저장했습니다.' - : '파일은 저장됐지만 백업 상태를 갱신하지 못했습니다. 앱을 다시 열어 백업 상태를 확인하고 필요하면 다시 내보내주세요.'; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); - if (saved == BackupExportResult.saved) await _loadFreshness(); - }); + await _loadFreshness(); + } catch (error) { + if (_routeCurrent && operationGeneration == _workflow.generation) { + _showError(error); + } + } finally { + if (mounted) { + setState(() { + _busy = false; + _working = false; + }); + } + } } Future _restore() async { - final password = await _askPassword(confirm: false); - if (password == null) return; - await _run(() async { - final candidate = await getIt().selectAndPreviewRestore( - password, - ); - if (!mounted || candidate == null) return; + if (_busy) return; + setState(() => _busy = true); + final operationGeneration = _workflow.generation; + try { + final password = await _askPassword(confirm: false); + if (password == null || + !_routeCurrent || + operationGeneration != _workflow.generation) { + return; + } + final generation = _workflow.generation; + setState(() => _working = true); + final candidate = await _workflow.preview(password); + if (!_routeCurrent || + generation != _workflow.generation || + candidate == null) { + return; + } + setState(() => _working = false); final preview = candidate.preview; + if (!mounted) return; final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('복원 내용 확인'), - content: Text( - '백업 시점: ${preview.cutoff.toLocal()}\n' - '앱 버전: ${preview.sourceAppVersion}\n' - '원본 플랫폼: ${preview.sourcePlatform}\n' - '일정 ${preview.scheduleCount}개\n' - '준비 템플릿 ${preview.templateCount}개\n' - '기본 준비 단계 ${preview.defaultPreparationStepCount}개\n\n' - '현재 로컬 데이터는 모두 교체됩니다.', + title: Text(l10n.dataRestorePreviewTitle), + content: SingleChildScrollView( + child: Text( + l10n.dataRestorePreview( + '${preview.cutoff.toLocal()}', + preview.sourceAppVersion, + preview.sourcePlatform, + preview.scheduleCount, + preview.templateCount, + preview.defaultPreparationStepCount, + ), + ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), - child: const Text('취소'), + child: Text(l10n.dataCancel), ), FilledButton( onPressed: () => Navigator.pop(context, true), - child: const Text('복원'), + child: Text(l10n.dataRestoreAction), ), ], ), ); - if (confirmed != true || !mounted) return; - await getIt().applyRestore(candidate); - await getIt()(); - if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('백업을 복원했습니다.'))); - await _loadFreshness(); - }); + if (confirmed != true || !_routeCurrent) return; + setState(() => _working = true); + final receipt = await _workflow.restore(candidate); + if (!_routeCurrent || receipt.generation != _workflow.generation) return; + setState(() => _restoreReceipt = receipt); + if (receipt.committed) { + _message( + receipt.followUpPending + ? l10n.dataRestorePartial + : l10n.dataRestoreComplete, + ); + await _loadFreshness(); + } else { + _showError( + DataOperationException( + receipt.failure ?? DataOperationFailure.failed, + ), + ); + } + } catch (error) { + if (_routeCurrent && operationGeneration == _workflow.generation) { + _showError(error); + } + } finally { + if (mounted) { + setState(() { + _busy = false; + _working = false; + }); + } + } } - Future _reset() async { - final confirmed = await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => const LocalDataResetConfirmationScreen(), - ), - ); - if (confirmed != true || !mounted) return; + Future _retryRestoreDelivery() async { + final receipt = _restoreReceipt; + if (_busy || receipt == null) return; setState(() => _busy = true); try { - final result = await getIt().reset(); - if (mounted) setState(() => _resetResult = result); + final result = await _workflow.retryFollowUp(receipt); + if (!_routeCurrent || result.generation != _workflow.generation) return; + setState(() => _restoreReceipt = result); + if (!result.followUpPending) _message(l10n.dataDeliveryUpdated); + } finally { + if (mounted) { + setState(() { + _busy = false; + _working = false; + }); + } + } + } + + Future _resetData() async { + if (_busy) return; + setState(() => _busy = true); + final generation = _workflow.generation; + try { + final confirmed = await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const LocalDataResetConfirmationScreen(), + ), + ); + if (confirmed != true || + !_routeCurrent || + generation != _workflow.generation) { + return; + } + setState(() => _working = true); + final result = await _reset(); + if (_routeCurrent) setState(() => _resetResult = result); } catch (error) { - if (!mounted) return; - setState(() => _busy = false); - _showError(error); + if (_routeCurrent && generation == _workflow.generation) { + _showError(error); + } + } finally { + if (mounted) { + setState(() { + _busy = false; + _working = false; + }); + } } } @@ -219,21 +380,23 @@ class _MyDataScreenState extends State { builder: (context) => _BackupPasswordDialog(confirm: confirm), ); - Future _run(Future Function() action) async { - setState(() => _busy = true); - try { - await action(); - } catch (error) { - if (mounted) _showError(error); - } finally { - if (mounted) setState(() => _busy = false); - } + void _message(String message) { + final messenger = ScaffoldMessenger.of(context); + messenger.clearSnackBars(); + messenger.showSnackBar(SnackBar(content: Text(message))); } void _showError(Object error) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('작업을 완료하지 못했습니다: $error'))); + final code = error is DataOperationException + ? error.failure + : DataOperationFailure.failed; + _message(switch (code) { + DataOperationFailure.busy => l10n.dataBusy, + DataOperationFailure.unavailable => l10n.dataUnavailable, + DataOperationFailure.stalePreview => l10n.dataStalePreview, + DataOperationFailure.invalidBackup => l10n.dataInvalidBackup, + DataOperationFailure.failed => l10n.dataOperationFailed, + }); } } @@ -317,47 +480,56 @@ class _BackupPasswordDialogState extends State<_BackupPasswordDialog> { super.dispose(); } + AppLocalizations get l10n => + AppLocalizations.of(context) ?? + lookupAppLocalizations(const Locale('ko')); + @override Widget build(BuildContext context) => AlertDialog( - title: Text(widget.confirm ? '백업 비밀번호 만들기' : '백업 비밀번호 입력'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: first, - obscureText: true, - enableSuggestions: false, - autocorrect: false, - autofillHints: null, - decoration: const InputDecoration( - labelText: '백업 비밀번호', - helperText: '15~128자, 대소문자와 공백을 그대로 구분합니다.', - ), - ), - if (widget.confirm) ...[ - const SizedBox(height: 12), + title: Text( + widget.confirm ? l10n.dataCreatePassword : l10n.dataEnterPassword, + ), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ TextField( - controller: second, + controller: first, obscureText: true, enableSuggestions: false, autocorrect: false, autofillHints: null, - decoration: const InputDecoration(labelText: '백업 비밀번호 확인'), - ), - ], - if (error != null) ...[ - const SizedBox(height: 8), - Text( - error!, - style: TextStyle(color: Theme.of(context).colorScheme.error), + decoration: InputDecoration( + labelText: l10n.dataPassword, + helperText: l10n.dataPasswordHelp, + helperMaxLines: 4, + ), ), + if (widget.confirm) ...[ + const SizedBox(height: 12), + TextField( + controller: second, + obscureText: true, + enableSuggestions: false, + autocorrect: false, + autofillHints: null, + decoration: InputDecoration(labelText: l10n.dataConfirmPassword), + ), + ], + if (error != null) ...[ + const SizedBox(height: 8), + Text( + error!, + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ], ], - ], + ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('취소'), + child: Text(l10n.dataCancel), ), FilledButton( onPressed: () { @@ -366,18 +538,15 @@ class _BackupPasswordDialogState extends State<_BackupPasswordDialog> { if (widget.confirm && parsed.normalized != BackupPassword.parse(second.text).normalized) { - throw const FormatException('비밀번호가 서로 다릅니다.'); + setState(() => error = l10n.dataPasswordMismatch); + return; } Navigator.pop(context, parsed.normalized); - } on FormatException catch (exception) { - setState( - () => error = exception.message == '비밀번호가 서로 다릅니다.' - ? exception.message - : '백업 비밀번호는 15~128자로 입력해주세요.', - ); + } on FormatException { + setState(() => error = l10n.dataPasswordInvalid); } }, - child: const Text('계속'), + child: Text(l10n.dataContinue), ), ], ); diff --git a/lib/presentation/startup/screens/local_data_recovery_screen.dart b/lib/presentation/startup/screens/local_data_recovery_screen.dart index fac95d41..c8306b48 100644 --- a/lib/presentation/startup/screens/local_data_recovery_screen.dart +++ b/lib/presentation/startup/screens/local_data_recovery_screen.dart @@ -1,9 +1,10 @@ -import 'package:on_time_front/core/database/local_reset_protocol.dart'; +import 'package:on_time_front/l10n/app_localizations.dart'; +import 'package:on_time_front/domain/entities/local_reset_result.dart'; import 'package:on_time_front/presentation/startup/screens/local_reset_progress_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:on_time_front/core/database/local_data_reset_service.dart'; +import 'package:on_time_front/domain/use-cases/local_data_workflows.dart'; import 'package:on_time_front/core/di/di_setup.dart'; import 'package:on_time_front/presentation/app/bloc/auth/auth_bloc.dart'; import 'package:on_time_front/presentation/recurring/recurrence_components.dart'; @@ -34,7 +35,7 @@ class _LocalDataRecoveryScreenState extends State { if (resetResult != null) { return LocalResetProgressScreen( initialResult: resetResult, - operation: widget.reset ?? getIt().reset, + operation: widget.reset ?? getIt().call, ); } return PopScope( @@ -178,8 +179,7 @@ class _LocalDataRecoveryScreenState extends State { _error = null; }); try { - final result = - await (widget.reset ?? getIt().reset)(); + final result = await (widget.reset ?? getIt().call)(); if (!mounted) return; if (result.isComplete && widget.onResetComplete != null) { widget.onResetComplete!(); @@ -190,7 +190,10 @@ class _LocalDataRecoveryScreenState extends State { if (mounted) { setState(() { _busy = false; - _error = '초기화하지 못했습니다. 다시 시도해 주세요.'; + _error = + (AppLocalizations.of(context) ?? + lookupAppLocalizations(const Locale('ko'))) + .dataResetFailed; }); } } diff --git a/test/core/backup/backup_restore_stale_contract_test.dart b/test/core/backup/backup_restore_stale_contract_test.dart new file mode 100644 index 00000000..b9fc97df --- /dev/null +++ b/test/core/backup/backup_restore_stale_contract_test.dart @@ -0,0 +1,70 @@ +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/backup/backup_crypto.dart'; +import 'package:on_time_front/core/backup/backup_service.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/services/app_metadata_service.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; +import '../../helpers/noop_alarm_cleanup.dart'; +import '../../helpers/sodium_test_loader.dart'; + +class _Metadata implements AppMetadataProvider { + @override + Future getMetadata() async => + const AppMetadata(version: '1', buildNumber: '1'); +} + +class _Cleanup extends NoopAlarmCleanup { + int calls = 0; + @override + Future forDataReplacement() async { + calls++; + } +} + +void main() { + test( + 'preview followed by a durable edit never overwrites that edit', + () async { + final db = AppDatabase.forTesting(NativeDatabase.memory()); + final gate = LocalDataOperationGate(); + final cleanup = _Cleanup(); + addTearDown(db.close); + addTearDown(gate.dispose); + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: 'original', + ), + ); + final service = BackupService( + db, + _Metadata(), + cleanup, + operationGate: gate, + crypto: BackupCrypto(sodiumLoader: loadSodiumForTest), + ); + const password = 'synthetic backup password'; + final preview = await service.previewEncryptedBackup( + await service.createEncryptedBackup(password), + password, + ); + await db.userDao.updateSpareTime( + 'local-profile', + const Duration(minutes: 17), + ); + Object? failure; + try { + await service.applyRestore(preview); + } catch (error) { + failure = error; + } + expect((await db.select(db.users).getSingle()).spareTime, 17); + expect(failure, isNotNull); + expect(cleanup.calls, 0); + expect(gate.generation, 0); + }, + ); +} diff --git a/test/core/database/alarm_replacement_integration_test.dart b/test/core/database/alarm_replacement_integration_test.dart index f58ae64e..610b7578 100644 --- a/test/core/database/alarm_replacement_integration_test.dart +++ b/test/core/database/alarm_replacement_integration_test.dart @@ -106,7 +106,11 @@ void main() { r.fallback.throwOnCancelIds.add('old'); await expectLater( service.applyRestore(restore), - throwsA(isA()), + throwsA( + isA() + .having((e) => e.followUpPending, 'cleanup remains pending', true) + .having((e) => e.generation, 'actual claim generation', 1), + ), ); expect( (await database.select(database.users).getSingle()).note, @@ -185,9 +189,17 @@ void main() { final completed = await service.reset(); expect(completed.isComplete, true); // A late progress-screen mount must not start a second destructive reset. - await (await SharedPreferences.getInstance()).setString('post-reset-sentinel', 'keep'); + await (await SharedPreferences.getInstance()).setString( + 'post-reset-sentinel', + 'keep', + ); expect(await service.reset(), same(completed)); - expect((await SharedPreferences.getInstance()).getString('post-reset-sentinel'), 'keep'); + expect( + (await SharedPreferences.getInstance()).getString( + 'post-reset-sentinel', + ), + 'keep', + ); expect((await r.operations.journal.read()).ownership, isEmpty); // Even after cleanup succeeds, the old closed DB is not reopened for editing. expect(r.gate.isInvalidated, true); diff --git a/test/core/database/local_data_operation_gate_test.dart b/test/core/database/local_data_operation_gate_test.dart index 55501214..bb15b7b5 100644 --- a/test/core/database/local_data_operation_gate_test.dart +++ b/test/core/database/local_data_operation_gate_test.dart @@ -42,4 +42,29 @@ void main() { ); }, ); + test( + 'exclusive validation does not invalidate writers until accepted', + () async { + final gate = LocalDataOperationGate(); + final validation = Completer(); + var invoked = false; + final pending = gate.run( + () async { + invoked = true; + }, + replacesData: true, + validateReplacement: () => validation.future, + ); + expect(gate.isAvailable, false); + expect(gate.isReplacingData, false); + expect(gate.generation, 0); + final failure = expectLater(pending, throwsStateError); + validation.completeError(StateError('stale')); + await failure; + expect(invoked, false); + expect(gate.isAvailable, true); + expect(gate.generation, 0); + gate.dispose(); + }, + ); } diff --git a/test/domain/use-cases/local_data_workflows_test.dart b/test/domain/use-cases/local_data_workflows_test.dart new file mode 100644 index 00000000..db7b3876 --- /dev/null +++ b/test/domain/use-cases/local_data_workflows_test.dart @@ -0,0 +1,247 @@ +import 'dart:async'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/backup/backup_crypto.dart'; +import 'package:on_time_front/core/backup/backup_service.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/core/services/app_metadata_service.dart'; +import 'package:on_time_front/data/adapters/local_data_workflow_adapters.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; +import 'package:on_time_front/domain/ports/local_data_ports.dart'; +import 'package:on_time_front/domain/use-cases/local_data_workflows.dart'; +import '../../helpers/noop_alarm_cleanup.dart'; +import '../../helpers/sodium_test_loader.dart'; + +class _Metadata implements AppMetadataProvider { + @override + Future getMetadata() async => + const AppMetadata(version: '1', buildNumber: '1'); +} + +class _Cleanup extends NoopAlarmCleanup { + int calls = 0; + Completer? barrier; + final entered = Completer(); + @override + Future forDataReplacement() async { + calls++; + if (!entered.isCompleted) entered.complete(); + if (barrier != null) await barrier!.future; + } +} + +class _Delivery implements RestoreDeliveryPort { + int calls = 0; + bool succeeds = false; + Completer? barrier; + @override + Future reconcile() async { + calls++; + return barrier?.future ?? succeeds; + } +} + +class _DelayedAdapter extends LocalBackupAdapter { + _DelayedAdapter(super.backup, this.gate); + final LocalDataOperationGate gate; + @override + Future apply(BackupRestoreInput input) async { + final committedGeneration = await super.apply(input); + await gate.run(() async {}, replacesData: true); + return committedGeneration; + } +} + +void main() { + late AppDatabase db; + late BackupService service; + late LocalDataOperationGate gate; + late _Cleanup cleanup; + late _Delivery delivery; + late BackupWorkflow workflow; + const password = 'synthetic backup password'; + setUp(() async { + db = AppDatabase.forTesting(NativeDatabase.memory()); + gate = LocalDataOperationGate(); + cleanup = _Cleanup(); + delivery = _Delivery(); + service = BackupService( + db, + _Metadata(), + cleanup, + crypto: BackupCrypto(sodiumLoader: loadSodiumForTest), + operationGate: gate, + ); + workflow = BackupWorkflow(LocalBackupAdapter(service), delivery); + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: 'backup sentinel', + ), + ); + }); + tearDown(() async { + await db.close(); + gate.dispose(); + }); + Future candidate() async => + service.previewEncryptedBackup( + await service.createEncryptedBackup(password), + password, + ); + Future revision() async => + (await db.select(db.users).getSingle()).dataRevision; + + test( + 'preclaim durable edit rejects without cleanup or generation change', + () async { + final input = await candidate(); + await db.userDao.updateSpareTime( + 'local-profile', + const Duration(minutes: 7), + ); + final before = await revision(); + final result = await workflow.restore(input); + expect(result.committed, false); + expect(result.failure, DataOperationFailure.stalePreview); + expect(result.followUpPending, false); + expect(cleanup.calls, 0); + expect(gate.generation, 0); + expect(await revision(), before); + expect((await db.select(db.users).getSingle()).spareTime, 7); + }, + ); + test( + 'replacement generation invalidates preview with no additional effects', + () async { + final input = await candidate(); + await gate.run(() async {}, replacesData: true); + final result = await workflow.restore(input); + expect(result.failure, DataOperationFailure.stalePreview); + expect(cleanup.calls, 0); + expect(gate.generation, 1); + }, + ); + test( + 'edit while cleanup awaits rejects transaction and exposes pending recovery', + () async { + final input = await candidate(); + cleanup.barrier = Completer(); + final pending = workflow.restore(input); + await cleanup.entered.future; + await db.userDao.updateSpareTime( + 'local-profile', + const Duration(minutes: 9), + ); + final before = await revision(); + cleanup.barrier!.complete(); + final result = await pending; + expect(result.committed, false); + expect(result.followUpPending, true); + expect(result.failure, DataOperationFailure.stalePreview); + expect(result.generation, 1); + expect((await db.select(db.users).getSingle()).spareTime, 9); + expect(await revision(), before); + delivery.succeeds = true; + final recovered = await workflow.retryFollowUp(result); + expect(recovered.committed, false); + expect(recovered.followUpPending, false); + expect(cleanup.calls, 1); + expect(await revision(), before); + }, + ); + test( + 'same token shares full pending receipt and delivery retry never reapplies', + () async { + final input = await candidate(); + delivery.barrier = Completer(); + final first = workflow.restore(input); + final second = workflow.restore(input); + expect(identical(first, second), true); + await cleanup.entered.future; + while (delivery.calls == 0) { + await Future.delayed(Duration.zero); + } + expect(identical(workflow.restore(input), first), true); + final committedRevision = await revision(); + delivery.barrier!.complete(false); + final partial = await first; + expect(partial.committed, true); + expect(partial.followUpPending, true); + expect(partial.generation, 1); + delivery.barrier = null; + delivery.succeeds = true; + final complete = await workflow.retryFollowUp(partial); + expect(complete.followUpPending, false); + expect(cleanup.calls, 1); + expect(await revision(), committedRevision); + expect((await workflow.restore(input)).followUpPending, false); + expect(cleanup.calls, 1); + expect(delivery.calls, 2); + await expectLater( + service.applyRestore(input), + throwsA( + isA().having( + (e) => e.failure, + 'consumed token', + DataOperationFailure.stalePreview, + ), + ), + ); + expect(await revision(), committedRevision); + }, + ); + test( + 'failed actual replacement transaction preserves data and retry commits once', + () async { + final input = await candidate(); + final before = await revision(); + await db.customStatement( + "CREATE TRIGGER fail_restore BEFORE INSERT ON users BEGIN SELECT RAISE(ABORT, 'synthetic'); END", + ); + final result = await workflow.restore(input); + expect(result.committed, false); + expect(result.followUpPending, true); + expect(await revision(), before); + expect((await db.select(db.users).getSingle()).note, 'backup sentinel'); + await db.customStatement('DROP TRIGGER fail_restore'); + delivery.succeeds = true; + final retry = await workflow.restore(input); + expect(retry.committed, true); + expect(await revision(), before + 1); + expect(gate.generation, 2); + }, + ); + test( + 'claim generation survives a later replacement before continuation', + () async { + final input = await candidate(); + workflow = BackupWorkflow(_DelayedAdapter(service, gate), delivery); + final result = await workflow.restore(input); + expect(result.committed, true); + expect(result.generation, 1); + expect(gate.generation, 2); + expect(result.followUpPending, true); + expect(result.failure, DataOperationFailure.stalePreview); + expect(delivery.calls, 0); + }, + ); + test('old delayed retry cannot absorb a newer generation receipt', () async { + final first = await workflow.restore(await candidate()); + delivery.barrier = Completer(); + final oldRetry = workflow.retryFollowUp(first); + final oldBarrier = delivery.barrier!; + delivery.barrier = null; + final second = await workflow.restore(await candidate()); + delivery.succeeds = true; + final newRetry = workflow.retryFollowUp(second); + expect(identical(oldRetry, newRetry), false); + expect((await newRetry).generation, 2); + oldBarrier.complete(true); + final old = await oldRetry; + expect(old.generation, 1); + expect(old.followUpPending, true); + }); +} diff --git a/test/presentation/my_page/my_data_export_test.dart b/test/presentation/my_page/my_data_export_test.dart index b4460fd2..48c399ce 100644 --- a/test/presentation/my_page/my_data_export_test.dart +++ b/test/presentation/my_page/my_data_export_test.dart @@ -1,3 +1,7 @@ +import 'package:on_time_front/data/adapters/local_data_workflow_adapters.dart'; +import 'package:on_time_front/domain/entities/local_reset_result.dart'; +import 'package:on_time_front/domain/ports/local_data_ports.dart'; +import 'package:on_time_front/domain/use-cases/local_data_workflows.dart'; import 'dart:async'; import 'package:flutter/material.dart'; @@ -6,16 +10,25 @@ import 'package:on_time_front/core/backup/backup_service.dart'; import 'package:on_time_front/core/di/di_setup.dart'; import 'package:on_time_front/presentation/my_page/my_data_screen.dart'; -class _BackupService extends Fake implements BackupService { +class _BackupService extends Fake implements BackupOperationsPort { late final result = Completer(); int calls = 0; + @override + int generation = 0; + Object? freshnessFailure; + Completer? freshnessPending; @override - Future getFreshness() async => - const BackupFreshnessStatus(freshness: BackupFreshness.neverExported); + Future freshness() async { + if (freshnessPending != null) return freshnessPending!.future; + if (freshnessFailure != null) throw freshnessFailure!; + return const BackupFreshnessStatus( + freshness: BackupFreshness.neverExported, + ); + } @override - Future exportToUserSelectedFile(String password) { + Future export(String password) { expect(password, 'test backup password'); calls++; return result.future; @@ -27,7 +40,9 @@ void main() { setUp(() async { await getIt.reset(); service = _BackupService(); - getIt.registerSingleton(service); + getIt.registerSingleton( + BackupWorkflow(service, _Delivery()), + ); }); tearDown(() => getIt.reset()); @@ -105,4 +120,79 @@ void main() { expect(find.text('암호화 백업을 저장했습니다.'), findsNothing); expect(find.byType(CircularProgressIndicator), findsNothing); }); + testWidgets( + 'freshness failure has safe retry and never exposes raw details', + (tester) async { + service.freshnessFailure = StateError('/private/synthetic/secret'); + await tester.pumpWidget(const MaterialApp(home: MyDataScreen())); + await tester.pumpAndSettle(); + expect(find.text('백업 상태를 확인하지 못했습니다. 다시 시도해 주세요.'), findsOneWidget); + expect(find.textContaining('/private/'), findsNothing); + service.freshnessFailure = null; + await tester.tap(find.text('다시 시도')); + await tester.pumpAndSettle(); + expect(find.text('아직 내보낸 백업이 없습니다.'), findsOneWidget); + }, + ); + testWidgets('late old-generation export error cannot show a snackbar', ( + tester, + ) async { + await startExport(tester); + service.generation++; + service.result.completeError(StateError('synthetic secret')); + await tester.pumpAndSettle(); + expect(find.byType(SnackBar), findsNothing); + expect(tester.takeException(), isNull); + }); + testWidgets('disposed route never shows late export result', (tester) async { + await startExport(tester); + await tester.pumpWidget(const MaterialApp(home: Text('new route'))); + service.result.complete(BackupExportResult.saved); + await tester.pumpAndSettle(); + expect(find.byType(SnackBar), findsNothing); + expect(find.text('new route'), findsOneWidget); + expect(tester.takeException(), isNull); + }); + testWidgets('late freshness failure after generation change is discarded', ( + tester, + ) async { + service.freshnessPending = Completer(); + await tester.pumpWidget(const MaterialApp(home: MyDataScreen())); + service.generation++; + service.freshnessPending!.completeError(StateError('old error')); + await tester.pumpAndSettle(); + expect(find.text('백업 상태를 확인하지 못했습니다. 다시 시도해 주세요.'), findsNothing); + expect(tester.takeException(), isNull); + }); + testWidgets( + 'reset confirmation is one intent and rejects a changed installation', + (tester) async { + var calls = 0; + final reset = LocalResetWorkflow( + RecoveryResetAdapter(() async { + calls++; + return const LocalResetResult(intentRecorded: true, completed: {}); + }), + ); + await tester.pumpWidget( + MaterialApp(home: MyDataScreen(resetWorkflow: reset)), + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('로컬 데이터 초기화')); + await tester.pumpAndSettle(); + expect(find.text('복구하지 않고 삭제하시겠습니까?'), findsOneWidget); + expect(calls, 0); + service.generation++; + await tester.tap(find.text('모두 삭제')); + await tester.pumpAndSettle(); + expect(calls, 0); + expect(find.text('내 데이터'), findsOneWidget); + expect(tester.takeException(), isNull); + }, + ); +} + +class _Delivery implements RestoreDeliveryPort { + @override + Future reconcile() async => true; } diff --git a/test/presentation/my_page/my_data_restore_test.dart b/test/presentation/my_page/my_data_restore_test.dart index 8ae15d12..9c2c1529 100644 --- a/test/presentation/my_page/my_data_restore_test.dart +++ b/test/presentation/my_page/my_data_restore_test.dart @@ -1,3 +1,6 @@ +import 'dart:async'; +import 'package:on_time_front/domain/ports/local_data_ports.dart'; +import 'package:on_time_front/domain/use-cases/local_data_workflows.dart'; import '../../helpers/noop_alarm_cleanup.dart'; import 'package:drift/drift.dart' show Value; import 'package:drift/native.dart'; @@ -19,35 +22,41 @@ class _Metadata implements AppMetadataProvider { const AppMetadata(version: '1.0.0', buildNumber: '1'); } -class _RestoreService extends Fake implements BackupService { +class _RestoreService extends Fake implements BackupOperationsPort { _RestoreService(this.delegate, this.candidate); final BackupService delegate; BackupRestoreCandidate? candidate; Object? failure; int applied = 0; + final appliedDone = Completer(); + @override + int get generation => delegate.generation; @override - Future getFreshness() => delegate.getFreshness(); + Future freshness() => delegate.getFreshness(); @override - Future selectAndPreviewRestore( - String password, - ) async { + Future preview(String password) async { expect(password, 'synthetic backup password'); if (failure != null) throw failure!; return candidate; } @override - Future applyRestore(BackupRestoreCandidate candidate) async { + Future apply(BackupRestoreInput candidate) async { applied++; - await delegate.applyRestore(candidate); + final generation = await delegate.applyRestoreWithReceipt( + candidate as BackupRestoreCandidate, + ); + appliedDone.complete(); + return generation; } } void main() { late AppDatabase database; late _RestoreService service; + late _Delivery delivery; setUp(() async { await getIt.reset(); @@ -73,7 +82,8 @@ void main() { .update(database.users) .write(const UsersCompanion(note: Value('current sentinel'))); service = _RestoreService(delegate, candidate); - getIt.registerSingleton(service); + delivery = _Delivery(); + getIt.registerSingleton(BackupWorkflow(service, delivery)); }); tearDown(() async { @@ -155,4 +165,44 @@ void main() { ); }, ); + testWidgets( + 'committed restore reports partial then retry changes no durable data', + (tester) async { + delivery.succeeds = false; + await select(tester); + await tester.tap(find.text('복원')); + await tester.pump(); + await tester.runAsync(() => service.appliedDone.future); + await tester.pumpAndSettle(); + expect( + find.textContaining('데이터는 복원됐지만 알림 처리가 완료되지 않았습니다.'), + findsWidgets, + ); + expect(service.applied, 1); + final before = await tester.runAsync( + () => database.select(database.users).getSingle(), + ); + expect(before!.note, 'synthetic backup'); + delivery.succeeds = true; + await tester.tap(find.text('알림 처리만 다시 시도')); + await tester.pumpAndSettle(); + expect(find.text('현재 데이터의 알림 처리를 완료했습니다.'), findsOneWidget); + expect(service.applied, 1); + final after = await tester.runAsync( + () => database.select(database.users).getSingle(), + ); + expect(after!.dataRevision, before.dataRevision); + expect(delivery.calls, 2); + }, + ); +} + +class _Delivery implements RestoreDeliveryPort { + bool succeeds = true; + int calls = 0; + @override + Future reconcile() async { + calls++; + return succeeds; + } } diff --git a/tool/qa/capture_local_data_workflows.dart b/tool/qa/capture_local_data_workflows.dart new file mode 100644 index 00000000..5eb8774d --- /dev/null +++ b/tool/qa/capture_local_data_workflows.dart @@ -0,0 +1,132 @@ +// Synthetic widget raster evidence. No mobile picker, OS delivery, or real data. +import 'dart:io'; +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/domain/entities/backup_operation.dart'; +import 'package:on_time_front/domain/ports/local_data_ports.dart'; +import 'package:on_time_front/domain/use-cases/local_data_workflows.dart'; +import 'package:on_time_front/l10n/app_localizations.dart'; +import 'package:on_time_front/presentation/my_page/my_data_screen.dart'; +import 'package:on_time_front/presentation/shared/theme/theme.dart'; + +class _Input extends BackupRestoreInput { + @override + BackupRestorePreview get preview => BackupRestorePreview( + cutoff: DateTime.utc(2026, 9, 24), + sourceAppVersion: '1.0', + sourcePlatform: 'android', + scheduleCount: 2, + templateCount: 1, + defaultPreparationStepCount: 3, + ); +} + +class _Backup implements BackupOperationsPort { + @override + int generation = 0; + bool freshnessFails = true; + @override + Future freshness() async { + if (freshnessFails) throw StateError('synthetic'); + return const BackupFreshnessStatus( + freshness: BackupFreshness.neverExported, + ); + } + + @override + Future export(String password) async => + BackupExportResult.saved; + @override + Future preview(String password) async => _Input(); + @override + Future apply(BackupRestoreInput input) async => ++generation; +} + +class _Delivery implements RestoreDeliveryPort { + @override + Future reconcile() async => false; +} + +void main() { + testWidgets('KO EN 200 percent data workflow states', (tester) async { + final output = Directory(Platform.environment['C01_CAPTURE_DIR']!); + await tester.runAsync(() => output.create(recursive: true)); + final font = FontLoader('Pretendard') + ..addFont(rootBundle.load('assets/fonts/Pretendard-Regular.ttf')); + await tester.runAsync(font.load); + final icons = FontLoader('MaterialIcons') + ..addFont(rootBundle.load('fonts/MaterialIcons-Regular.otf')); + await tester.runAsync(icons.load); + tester.view.physicalSize = const Size(430, 932); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + for (final language in ['ko', 'en']) { + final port = _Backup(); + final workflow = BackupWorkflow(port, _Delivery()); + final key = GlobalKey(); + await tester.pumpWidget( + RepaintBoundary( + key: key, + child: MaterialApp( + key: ValueKey(language), + debugShowCheckedModeBanner: false, + theme: themeData, + locale: Locale(language), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + builder: (context, child) => MediaQuery( + data: MediaQuery.of( + context, + ).copyWith(textScaler: const TextScaler.linear(2)), + child: child!, + ), + home: MyDataScreen(workflow: workflow), + ), + ), + ); + Future capture(String name) async { + await tester.pumpAndSettle(); + expect(tester.takeException(), isNull); + final boundary = + key.currentContext!.findRenderObject()! as RenderRepaintBoundary; + await tester.runAsync(() async { + final image = await boundary.toImage(pixelRatio: 2); + final bytes = await image.toByteData(format: ui.ImageByteFormat.png); + await File( + '${output.path}/$name-$language.png', + ).writeAsBytes(bytes!.buffer.asUint8List(), flush: true); + image.dispose(); + }); + } + + await capture('freshness-failed'); + port.freshnessFails = false; + await tester.tap(find.text(language == 'ko' ? '다시 시도' : 'Try again')); + await tester.pumpAndSettle(); + final restore = find.text( + language == 'ko' ? '백업에서 복원' : 'Restore from backup', + ); + await tester.ensureVisible(restore); + await tester.tap(restore); + await tester.pumpAndSettle(); + await capture('password'); + await tester.enterText( + find.byType(TextField), + 'synthetic backup password', + ); + await tester.tap(find.text(language == 'ko' ? '계속' : 'Continue')); + await tester.pumpAndSettle(); + await capture('preview'); + await tester.tap(find.text(language == 'ko' ? '복원' : 'Restore')); + await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 5)); + await tester.pumpAndSettle(); + await tester.drag(find.byType(ListView), const Offset(0, 800)); + await capture('restore-pending'); + } + }); +} From aa2fda68fbf3ac431738de3792f5d587d35b6dda Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:54:52 +0900 Subject: [PATCH 47/59] docs: record C01 regression proof and remaining integration work --- handoff/audit-2026-09-23-progress.md | 4 + plans/audit-2026-09-23/README.md | 2 +- .../artifacts/c01/c01-analyze-final.log | 2 + .../artifacts/c01/c01-codegen.log | 36 + .../artifacts/c01/c01-coverage-gate.log | 1 + .../artifacts/c01/c01-focused-final.log | 55 + .../artifacts/c01/c01-full-coverage.log | 974 ++++++++++++++++++ .../artifacts/c01/c01-generated-policy.log | 1 + .../artifacts/c01/c01-l10n.log | 2 + .../artifacts/c01/c01-local-only.log | 1 + .../artifacts/c01/c01-manifest.log | 1 + .../artifacts/c01/c01-negative-proof.json | 14 + .../artifacts/c01/c01-negative.log | 14 + .../artifacts/c01/c01-python.log | 16 + .../artifacts/c01/c01-review-transcript.md | 29 + .../artifacts/c01/c01-source-freeze.json | 29 + .../audit-2026-09-23/artifacts/c01/c01-ui.log | 3 + .../artifacts/c01/freshness-failed-en.png | Bin 0 -> 154557 bytes .../artifacts/c01/freshness-failed-ko.png | Bin 0 -> 103153 bytes .../artifacts/c01/password-en.png | Bin 0 -> 124505 bytes .../artifacts/c01/password-ko.png | Bin 0 -> 69763 bytes .../artifacts/c01/preview-en.png | Bin 0 -> 143753 bytes .../artifacts/c01/preview-ko.png | Bin 0 -> 98830 bytes .../artifacts/c01/restore-pending-en.png | Bin 0 -> 188318 bytes .../artifacts/c01/restore-pending-ko.png | Bin 0 -> 129815 bytes .../c01-final-validation.json | 204 ++++ plans/audit-2026-09-23/issues/C01.md | 45 + plans/audit-2026-09-23/state.json | 23 +- 28 files changed, 1450 insertions(+), 6 deletions(-) create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-analyze-final.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-codegen.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-coverage-gate.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-focused-final.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-full-coverage.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-generated-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-l10n.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-local-only.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-manifest.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-negative-proof.json create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-negative.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-python.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-review-transcript.md create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-source-freeze.json create mode 100644 plans/audit-2026-09-23/artifacts/c01/c01-ui.log create mode 100644 plans/audit-2026-09-23/artifacts/c01/freshness-failed-en.png create mode 100644 plans/audit-2026-09-23/artifacts/c01/freshness-failed-ko.png create mode 100644 plans/audit-2026-09-23/artifacts/c01/password-en.png create mode 100644 plans/audit-2026-09-23/artifacts/c01/password-ko.png create mode 100644 plans/audit-2026-09-23/artifacts/c01/preview-en.png create mode 100644 plans/audit-2026-09-23/artifacts/c01/preview-ko.png create mode 100644 plans/audit-2026-09-23/artifacts/c01/restore-pending-en.png create mode 100644 plans/audit-2026-09-23/artifacts/c01/restore-pending-ko.png create mode 100644 plans/audit-2026-09-23/c01-final-validation.json diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index a67795ef..093e0263 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -122,3 +122,7 @@ PR 승인 대기 본문은 질문 당시 내용(A06원격CI+28개 이슈)으로 C01 수정전 음성검증: baseline3771615d에서 preview후17분으로편집한durable값이restore후0분으로덮여실패. agent가BackupService/gate두파일을본인WIP해시백업후일시교체/원복했고사전사후해시일치 /tmp/c01-negative-proof.json 보존. root소스검토재개가능. C01Flutter독점/확장focused/캡처진행중. A07원격최종28baf6c7 양CI성공: Flutter35947757749=944/analyze88.70%; Android35947757745 artifact10786864301 synthetic5d57fde334649f0d3cdc1036f54e00d3e6cca431/APKc060bad43f3d66d5337bfc8f934ba0f3f3070ab369361ab8c39df9ed1cf6db29. a07-ci-validation.json 및 #599본문갱신. 기기/merge미완료. + +### C01 구현 전달 및 다음 A08 + +C01 source4529d9f8, 전체959/88.74%, 소스24/commit 해시 일치, KOEN200%8장 root 확인. 실제 음성 회귀와 추가 문답은 c01-final-validation.json/artifacts/c01에 보존. #600 OPEN: A08 aggregate, A09 staging/runtime/writer fencing, D02 손상 복원 및 실기기 미완료. A08 전담 /root/implement_a08에 Flutter 독점권 전달. R04#618 게시·readback 완료,32/64. R01 전담 /root/grill_r01 문답 진행. PR585 본문 승인 질문은 여전히 미응답이며 동결 payload를 변경/재게시하지 않음. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 0bf0900f..2dd61a3d 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -29,7 +29,7 @@ | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 독립 journal·복구/host kill11, main통합 원격882 tests/88.50%·APK 통과,기기 대기 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | d06474d4 실제 타이머 전환 구현,47경계 및 원격929전체·88.79%·APK 통과,기기 대기 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | d73c7863 원자적 시작·partial retry 구현,원격944 tests/88.70%·APK 및 KOEN200% 검증,기기 대기 | -| C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 실제3문답·staging/gate 보충 포함, 구체 workflow·부분 결과·통합 의존성 확정 | +| C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 4529d9f8 백업/reset workflow 구현,959 tests·88.74%·KOEN200% 통과,A08/A09/D02 통합 대기 | | A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 실제4문답·원자 저장/조회·충돌/멱등/소유권 기준 확정 | | A09 | [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | 실제4문답·staging/runtime 세대·복원 후 정리 기준 확정 | | D01 | [#603](https://github.com/DevKor-github/OnTime-front/issues/603) | 실제4문답·DB 비의존 시작 복구·키/파일 보존 기준 확정 | diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-analyze-final.log b/plans/audit-2026-09-23/artifacts/c01/c01-analyze-final.log new file mode 100644 index 00000000..f41bfd3e --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-analyze-final.log @@ -0,0 +1,2 @@ +Analyzing OnTime-front... +No issues found! (ran in 1.6s) diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-codegen.log b/plans/audit-2026-09-23/artifacts/c01/c01-codegen.log new file mode 100644 index 00000000..18776cef --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-codegen.log @@ -0,0 +1,36 @@ +W These options have been removed and were ignored: --delete-conflicting-outputs + 0s freezed on 493 inputs; lib/core/backup/backup_crypto.dart + 0s freezed on 493 inputs: 1 skipped; lib/core/backup/backup_file_export_port.dart +W SDK language version 3.12.0 is newer than `analyzer` language version 3.10.0. Run `flutter packages upgrade`. + 5s freezed on 493 inputs: 3 skipped, 1 no-op; spent 5s analyzing; lib/core/backup/recurring_backup_data.dart + 6s freezed on 493 inputs: 288 skipped, 40 no-op; spent 5s analyzing; lib/presentation/shared/router/route_arguments.dart + 6s freezed on 493 inputs: 446 skipped, 47 no-op; spent 5s analyzing + 0s json_serializable on 986 inputs; lib/core/backup/backup_crypto.dart + 1s json_serializable on 986 inputs: 794 skipped, 192 no-op + 0s drift_dev on 3944 inputs; lib/core/backup/backup_crypto.dart + 0s drift_dev on 3944 inputs: 1510 skipped, 20 output, 331 same, 2 no-op; test/presentation/home/screens/home_screen_tmp_test.freezed.dart + 1s drift_dev on 3944 inputs: 3368 skipped, 20 output, 362 same, 194 no-op + 0s source_gen:combining_builder on 1972 inputs; lib/core/backup/backup_crypto.dart + 0s source_gen:combining_builder on 1972 inputs: 1967 skipped, 5 no-op + 0s mockito:mockBuilder on 1144 inputs; test/core/backup/backup_crypto_test.dart + 0s mockito:mockBuilder on 1144 inputs: 1065 skipped, 79 no-op + 0s injectable_generator:injectable_builder on 5088 inputs; lib/core/backup/backup_crypto.dart + 0s injectable_generator:injectable_builder on 5088 inputs: 2183 skipped, 2 output, 26 same, 62 no-op; lib/presentation/schedule_create/schedule_place_moving_time/cubit/schedule_place_moving_time_cubit.dart.types.temp.dart + 0s injectable_generator:injectable_builder on 5088 inputs: 4896 skipped, 2 output, 26 same, 164 no-op + 0s injectable_generator:injectable_config_builder on 5088 inputs; lib/core/backup/backup_crypto.dart +W injectable_generator:injectable_config_builder on lib/core/di/di_setup.dart: + Missing dependencies in on_time_front/core/di/di_setup.dart + + [AlarmRepositoryImpl] depends on unregistered type [RecurringScheduleRepository] from package:on_time_front/domain/repositories/recurring_schedule_repository.dart + [ScheduleRepositoryImpl] depends on unregistered type [RecurringScheduleRepository] from package:on_time_front/domain/repositories/recurring_schedule_repository.dart + [CreateScheduleFormSubmissionUseCase] depends on unregistered type [RecurringSchedulesUseCase] from package:on_time_front/domain/use-cases/recurring_schedules_use_case.dart + [UpdateScheduleFormSubmissionUseCase] depends on unregistered type [RecurringSchedulesUseCase] from package:on_time_front/domain/use-cases/recurring_schedules_use_case.dart + [AuthBloc] depends on unregistered type [RuntimePrivacyMigration] from package:on_time_front/core/services/runtime_privacy_migration.dart + [ScheduleFormBloc] depends on unregistered type [RecurringSchedulesUseCase] from package:on_time_front/domain/use-cases/recurring_schedules_use_case.dart + + Did you forget to annotate the above class(s) or their implementation with @injectable? + or add the right environment keys? + ------------------------------------------------------------------------ + + 0s injectable_generator:injectable_config_builder on 5088 inputs: 5040 skipped, 1 output, 47 no-op + Built with build_runner/aot in 10s; wrote 411 outputs. diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-coverage-gate.log b/plans/audit-2026-09-23/artifacts/c01/c01-coverage-gate.log new file mode 100644 index 00000000..62f4748d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-coverage-gate.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Coverage: 88.74% (13507/15221 lines) diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-focused-final.log b/plans/audit-2026-09-23/artifacts/c01/c01-focused-final.log new file mode 100644 index 00000000..6212d3e5 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-focused-final.log @@ -0,0 +1,55 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:01 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:01 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:01 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:01 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:01 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:01 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:01 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:01 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:01 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:02 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:02 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:02 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:03 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:03 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:03 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:03 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:03 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: reset confirmation is one intent and rejects a changed installation +00:03 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:03 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:03 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:03 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:04 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: committed restore reports partial then retry changes no durable data +00:04 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: committed restore reports partial then retry changes no durable data +00:04 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:04 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: scope defaults to only this occurrence and supports following scope +00:04 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:04 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: failed reset remains recoverable and never reports success +00:04 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: partial reset opens recovery instead of reporting completion +00:05 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: privacy keeps all six policy sections scrollable +00:05 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: management detail and end confirmation keep independent preparation +00:05 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: (tearDownAll) +00:05 +52: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-full-coverage.log b/plans/audit-2026-09-23/artifacts/c01/c01-full-coverage.log new file mode 100644 index 00000000..7e48256c --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-full-coverage.log @@ -0,0 +1,974 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:01 +30: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart +00:01 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:02 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:02 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:02 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:02 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:02 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:02 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:02 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: only complete iOS OS observations resolve reconstructed uncertainty +00:02 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeUnmapped remains partial without guessing ownership +00:02 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeError remains partial without guessing ownership +00:02 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS fallbackError remains partial without guessing ownership +00:02 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS unownedNotification remains partial without guessing ownership +00:02 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: secure reset marker resumes deletion but Android uncertainty remains +00:03 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryMarked recovers through a new owner +00:03 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:03 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:03 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at verified recovers through a new owner +00:03 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:03 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:04 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:04 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: preserves case and surrounding spaces +00:04 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: rejects passwords outside the 15 to 128 code point boundary +00:04 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: sends encrypted bytes and name only; saved receipt has no location +00:04 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: cancel is distinct from success +00:04 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: unrecognized native response null is never success +00:04 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: unrecognized native response is never success +00:04 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_file_export_port_test.dart: unrecognized native response unknown is never success +00:04 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:05 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:05 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:05 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:05 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:05 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:05 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: real iOS selected path is read and authenticated before preview +00:06 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:06 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:06 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:06 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:06 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:06 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:06 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:06 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:07 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: independent file retains only cancellation identity across reopen +00:07 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at written reopens a complete committed generation +00:07 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at verified reopens a complete committed generation +00:07 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:07 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at readBack reopens a complete committed generation +00:07 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: staged first write is preserved and never reported as an empty installation +00:07 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: truncated staged first write is preserved and blocks new ownership +00:07 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {broken +00:07 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {"version":2} +00:07 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {"version":1,"ownership":[]} +00:07 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: backup exclusion failure prevents creating ownership bytes +00:07 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: only a fully completed reset journal can be removed +00:07 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: duplicate actual platform IDs are rejected instead of hiding ownership +00:07 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: impossible completion states cannot be written over a valid journal +00:08 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: initializeLaunchHandling dispatches cold-start alarm payload +00:08 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload dispatches resumed alarm payload +00:08 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities maps native alarm provider capabilities +00:08 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats missing platform implementation as unsupported +00:08 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats native platform errors as unsupported +00:08 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats null native response as unsupported +00:08 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: checkPermission and requestPermission map native wire values +00:08 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat missing platform implementation as unsupported +00:08 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat native platform errors as unsupported +00:08 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm sends the native alarm contract payload +00:08 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm exposes permission failures as scheduling exception +00:08 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm maps unsupported and generic native errors +00:08 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm sends cancellation payload for scheduled records +00:08 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm reports missing native platform as unconfirmed +00:08 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm maps native platform failures to scheduling exception +00:08 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelAllNativeAlarms cancels every provided record in order +00:08 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload ignores invalid and failed payload reads +00:08 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=false snapshot after persistence +00:08 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=true snapshot after persistence +00:08 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=false snapshot after persistence +00:08 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=true snapshot after persistence +00:08 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: legacy and corrupted content cannot bypass reconciliation at either provider +00:08 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancel uses the plugin API for pending and presented IDs, never cancelAll +00:08 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancellation errors are visible to reconciliation +00:08 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/app_metadata_service_test.dart: package info metadata provider returns runtime app metadata +00:08 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: owned legacy pending is cancelled without clearing step notifications +00:09 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: cancel failure preserves only ownership tombstone and retry converges +00:09 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:09 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: successful cancel call without readback removal is not success +00:09 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: cold launch collects once and concurrent initialization is single flight +00:09 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: null +00:09 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:09 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:09 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: getter failure releases single flight and callback still works before retry +00:09 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback B arriving while cold A getter waits wins, raw legacy fields removed +00:09 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback before delegate installation preserves only safe latest payload +00:09 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: Noop to installed delegate cannot let slow cold A overwrite native B +00:09 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: data replacement invalidates delayed initial launch +00:09 +198: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart +00:09 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:09 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android read failure is unknown, not absent +00:10 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cancel needs public success and post read-back +00:10 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache absence does not hide cancel failure +00:10 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android remaining ID and failed post query are unconfirmed cancellations +00:10 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS OS observation preserves source and minimal ownership +00:10 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS read failure is unknown, not absent +00:10 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cancel needs public success and post read-back +00:10 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cache absence does not hide cancel failure +00:10 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS remaining ID and failed post query are unconfirmed cancellations +00:10 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit observation rejects unavailable, malformed and unrelated IDs +00:10 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit already absent is idempotent; present cancel requires fresh absence +00:10 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:10 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: platform cleanup failure is visible and retryable, without raw content +00:10 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: DB-unavailable cleanup removes legacy raw and does not touch unrelated data +00:10 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: localizedNotificationText selects Korean text only for Korean locale +00:10 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: isScheduleAlarmPayload detects native alarm payloads without suppressing schedule notifications +00:10 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule notification payload to the schedule start screen +00:10 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes five-minute prompts as early-start schedule starts +00:10 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule and preparation updates to the alarm screen +00:10 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString ignores null, invalid, and unrelated payloads +00:10 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: invalid schedule hints never route to nearest preparation +00:10 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForData routes decoded local payload data with the same rules +00:10 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:10 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:10 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:10 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:10 +232: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart +00:11 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:11 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: concurrent and repeated starts preserve first timestamp and one revision +00:11 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=false missing=false without overwriting durable first start +00:11 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real storage retains every failed platform ownership for the same Schedule +00:11 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=true missing=false without overwriting durable first start +00:11 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:11 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:11 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and create retries are idempotent +00:11 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:11 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:11 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:11 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:11 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk changes preserve only overridden fields and preparation +00:11 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: ending following leaves active and historical rows and their preparations intact +00:11 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: finite conflicts require explicit exclusion and excluded slots consume count +00:12 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: place edits never rename another occurrence or historical place +00:12 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk edit preserves active preparation and consumes its original count +00:12 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: stale first occurrence approval asks for review without changing durable data +00:12 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:12 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:12 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:12 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:12 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: an override cannot be moved into another occurrence preparation window +00:12 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:13 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: missing registry is not mistaken for a corrupt registry +00:13 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:13 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:13 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:13 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:13 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: replaceAll persists records and removes the key when cleared +00:13 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: spare time preserves preferences, scores and export metadata +00:13 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: spare time preserves preferences, scores and export metadata +00:13 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: spare time preserves preferences, scores and export metadata +00:13 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: preferences round trip preserves NotificationTiming.exact and ownership pending=false +00:13 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: repeated initial creation cannot replace an existing profile +00:13 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: repeated initial creation cannot replace an existing profile +00:13 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: repeated initial creation cannot replace an existing profile +00:13 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: repeated initial creation cannot replace an existing profile +00:13 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: same preferences and empty score reset are exact no-ops +00:13 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:13 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: multiple preparation steps are a no-op even when caller omits links +00:13 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:13 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:13 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: empty profile starts without a durable revision and creation is race safe +00:13 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedule date ranges as start-inclusive and end-exclusive +00:13 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: raw revision update notifies DB watchers and failed profile write publishes no success +00:13 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:13 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:13 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:13 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:13 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:13 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:13 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:13 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:14 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:14 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:14 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:14 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: saves and loads the early start timestamp per schedule +00:14 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: returns null for missing, corrupt, and incomplete session payloads +00:14 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: clearSession removes only the requested schedule session +00:14 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers map schedules to and from database rows preserving stored fields +00:14 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps users to and from database rows preserving profile values +00:14 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers empty users cannot be converted to database rows +00:14 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps places and preparation steps to database rows +00:14 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:14 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getUserById returns null for an unknown user +00:14 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getAllUsers returns all persisted users as domain entities +00:14 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return an empty preparation list when user has no steps +00:15 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:15 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps when rows are out of order +00:15 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should keep remaining preparation steps when a link is broken +00:15 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should return each preparation step once when links form a cycle +00:15 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should return one preparation step by id +00:15 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:15 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:15 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: updatePreparationUser should update a preparation step +00:15 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:15 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:15 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:15 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: daily intervals keep civil time and include the end date +00:15 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:15 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:16 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:16 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: new series starts at a future preparation and counts from there +00:16 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:16 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:16 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:16 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:16 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: updatePreparationUser should update a preparation step +00:16 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations createPreparationSchedule stores steps as an ordered linked list +00:16 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns empty for no steps +00:16 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId orders three linked steps when rows are out of order +00:16 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId keeps remaining steps when a link is broken +00:16 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns each step once when links form a cycle +00:16 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById returns the stored step contract +00:16 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:16 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:16 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:16 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:16 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:16 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:16 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation +00:16 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:16 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:16 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: skips a just-missed alarm instead of scheduling catch-up +00:16 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:16 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:16 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:16 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:16 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:16 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:16 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:16 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:16 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:16 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:16 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:16 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:16 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:16 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:16 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:16 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:16 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:16 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:16 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:16 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:16 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:16 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:16 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:16 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:16 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:16 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:16 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:16 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:16 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:16 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:16 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: deleted and finished schedules accept reconciliation before awaiting cancellation +00:16 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: deleted and finished schedules accept reconciliation before awaiting cancellation +00:16 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:16 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: reconciliation failures remain best-effort +00:16 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:16 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:16 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:16 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:16 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:16 +403: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart +00:17 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:17 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:17 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:17 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:17 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:17 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:17 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:17 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:17 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:17 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:17 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:18 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: pending callers share failure, then retry the same instance and timestamp +00:18 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: snapshot failure is partial and retry preserves a newer skip projection +00:18 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: replacement generation does not reuse an old flight or write old runtime +00:18 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: marker failure keeps durable run and a bounded retry preserves its clock +00:18 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish while start cleanup is pending cannot resurrect the run +00:18 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: dispose detaches the shared listener without releasing in-flight ownership +00:18 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:18 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure does not undo committed start or skip its snapshot +00:18 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure after finish still clears completed runtime +00:18 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:18 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:18 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:18 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:18 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:18 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:18 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:18 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:18 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:18 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:18 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:18 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:18 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:18 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:19 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:19 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:19 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:19 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:19 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:19 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:19 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:19 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:19 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:20 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:20 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:20 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:20 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:20 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:20 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:20 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:20 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:20 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: each cutoff finishes without waiting for later edits to become quiet +00:20 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failed snapshot completes its cutoff and still serves a newer request +00:20 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failure alone stops; a request from completion starts another pass +00:20 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: replacement waits for the actual late native Future before same-ID reuse +00:20 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: caller timeout cannot release ownership of an unreturned native Future +00:20 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: ordinary export busy does not invalidate a requested pass +00:20 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: replacement cancellation failure retains evidence and prevents replacement +00:20 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: delete mutation accepted during snapshot cannot be resurrected by replaceAll +00:20 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: registry write failure is not armed and a queued newer request recovers ownership +00:21 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: detailed OFF accepted after snapshot replaces old content in trailing pass +00:21 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: request after schedule snapshot receives its own latest pass +00:21 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:21 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=false +00:21 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:21 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:21 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: a registry-owned ID with a conflicting route is cancelled before repair +00:21 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=true +00:21 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=true +00:21 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=false +00:21 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=false +00:21 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native revoke and grant switch providers after cancelling the old registration +00:21 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:21 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:21 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:21 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:21 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:21 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:21 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:21 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:21 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:21 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:21 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:21 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:21 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: off retains a cleanup warning and denial never becomes precise +00:21 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: no future registrations never becomes precise from capability +00:21 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: iOS alarm and notification language remains capability appropriate +00:21 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:21 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: empty user exposes null convenience values +00:22 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:22 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:22 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:22 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:22 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:22 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:22 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities calculates left time before leave with explicit now +00:22 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports late status after leave time has passed +00:22 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities fast-forwards preparation steps for late entry +00:22 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities single preparation step tracks elapsed time and diagnostics +00:22 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities marks all steps done for very late entry inside schedule window +00:22 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports display helpers for current and completed steps +00:22 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities skipCurrentStep marks only the active step done +00:22 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities completed and empty preparations expose safe display fallbacks +00:22 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities cache fingerprint changes when preparation step name changes +00:22 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities schedule with preparation derives start time and total duration +00:22 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities combines a schedule entity with timed preparation preserving fields +00:22 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleEntity diagnostics string representation includes schedule identity for diagnostics +00:22 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleState timing helper durationUntilPreparationStartAt uses injected now deterministically +00:22 +519: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart +00:22 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:22 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:23 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:23 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:23 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: minimal progress hydrates names/durations from current source only +00:23 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy exact fingerprint additionally requires all step fields +00:23 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy launch becomes a minimal versioned route, never a start command +00:23 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:23 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:23 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:23 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:23 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:23 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (setUpAll) +00:23 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:23 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:24 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:24 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:24 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:24 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:24 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: filled date tile uses built-in selected-day colors +00:24 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:24 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:24 +549: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:24 +550: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:24 +551: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:24 +552: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: compact height clamps calendar rows without overflow +00:24 +553: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:25 +554: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:25 +555: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:25 +556: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits without scroll at 1.3 text scale +00:25 +557: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: HomeScreenTmp subscribes monthly bloc for today +00:25 +558: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart +00:25 +558: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits when today schedule exists +00:25 +559: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: regular portrait home matches Figma hero and card geometry +00:25 +560: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: banner clears the device safe area before rendering +00:25 +561: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: today schedule tile truncates long schedule names +00:25 +562: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: does not reopen schedule start on started-state ticks +00:25 +563: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: view calendar button navigates to calendar screen +00:25 +564: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: today upcoming tile opens early-start schedule start route +00:25 +565: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: today ongoing tile opens active alarm route +00:25 +566: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: animated arc indicator reaches requested score +00:25 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (tearDownAll) +00:26 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +568: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +569: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +570: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +571: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +572: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +573: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +574: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +576: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +577: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +578: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +579: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +580: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +581: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +582: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +583: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +584: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +585: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +586: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +588: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +589: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +590: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +591: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:26 +592: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/bloc/monthly_schedules_bloc_test.dart: month already in loaded range reuses cached range without reloading +00:26 +593: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: preparation fallback is shown as dash when unavailable +00:26 +594: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: preparation fallback is shown as dash when unavailable +00:26 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: preparation fallback is shown as dash when unavailable +00:26 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: preparation fallback is shown as dash when unavailable +00:26 +597: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: preparation fallback is shown as dash when unavailable +00:26 +598: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: preparation fallback is shown as dash when unavailable +00:26 +599: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: preparation fallback is shown as dash when unavailable +00:26 +600: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: swipe action button height increases when schedule is expanded +00:27 +601: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: swipe action button height increases when schedule is expanded +00:27 +602: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: swipe action button height increases when schedule is expanded +00:27 +603: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: swipe action button height increases when schedule is expanded +00:27 +604: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: swipe action button height increases when schedule is expanded +00:27 +605: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: swipe action button height increases when schedule is expanded +00:27 +606: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: swipe action button height increases when schedule is expanded +00:27 +607: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:27 +608: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:27 +609: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:27 +610: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:27 +611: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:27 +612: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:27 +613: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +614: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:28 +615: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +616: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:28 +617: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:28 +618: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:28 +619: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:28 +620: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:28 +621: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: AppLifecycleState.hidden updates UI silently without later replay +00:28 +622: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +623: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +624: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +626: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +627: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +628: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +629: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +630: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +632: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +633: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +634: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +635: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +636: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +637: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +638: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +639: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +640: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +641: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +642: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +643: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +644: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +645: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +646: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +647: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +648: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +649: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +650: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +651: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +652: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +653: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +654: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +655: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +656: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +657: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +658: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +659: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +660: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +661: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +662: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +663: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +664: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +665: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +666: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +667: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +668: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +669: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +670: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +671: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +672: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +673: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +674: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +675: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +676: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:29 +677: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:29 +678: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:29 +679: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:29 +680: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:29 +681: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:29 +682: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +683: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +684: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +685: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +686: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +687: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +688: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:30 +689: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:30 +690: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:30 +691: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow same schedule re-emission preserves started state when early-started +00:30 +692: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish clears timed preparation and early session state +00:30 +693: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow confirmation-required run stays stopped after prompt until explicit start +00:30 +694: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow mismatched fingerprint invalidates cached progress +00:30 +695: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start when early session and snapshot exist +00:30 +696: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start with only early session derives elapsed from session start +00:30 +697: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt loads schedule details and arms the prompt +00:30 +698: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects ended schedules and clears their alarm +00:31 +699: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects stale fingerprint unless it starts prep +00:31 +700: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm prompt validation failure clears only non-start prompts +00:31 +701: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish failure keeps the active schedule visible +00:31 +702: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: schedule events expose equality props for bloc deduping +00:31 +703: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/constants/app_colors_test.dart: white opacity colors keep the documented alpha ladder +00:31 +704: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/constants/app_colors_test.dart: brand swatches expose stable primary shades +00:31 +705: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/constants/app_colors_test.dart: social type string conversion is normalized for persistence +00:31 +706: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatTime describes elapsed seconds in Korean units +00:31 +707: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatTimeTimer uses timer display with hours when needed +00:31 +708: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatEalyLateTime reports absolute early or late minutes +00:31 +709: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatElapsedTime keeps seconds two-digit padded +00:31 +710: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:31 +711: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default marker when a scheduled day is not selected +00:31 +712: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default empty-day behavior when there are no events +00:31 +713: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:32 +714: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +715: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +716: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +717: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +718: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +719: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +720: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +721: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:32 +722: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:32 +723: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:33 +724: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:33 +725: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:33 +726: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:33 +727: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:33 +728: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: date picker saves the initial date value +00:33 +729: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (setUpAll) +00:33 +729: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: button styles resolve enabled and disabled visual contracts +00:34 +730: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme builds app calendar tokens and copies overrides +00:34 +731: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:34 +732: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:34 +733: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:34 +734: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (tearDownAll) +00:34 +734: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:34 +735: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route exits back toward its entry edge +00:34 +736: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: bottom navigation routes exit back toward their entry edge +00:34 +737: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: (setUpAll) +00:34 +737: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:34 +738: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +739: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +740: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +741: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +742: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +743: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +744: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +745: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +746: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +747: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +748: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +749: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +750: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +751: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +752: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +753: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +754: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +755: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:36 +756: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:36 +757: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:36 +758: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:36 +759: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:36 +760: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:37 +761: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:37 +762: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:37 +763: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:37 +764: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:37 +765: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:37 +766: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:37 +767: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:37 +768: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:37 +769: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:38 +770: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:38 +771: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:38 +772: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:38 +773: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:38 +774: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:38 +775: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 430.0 +00:38 +776: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +777: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +778: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +779: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +780: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +781: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +782: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +783: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +784: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +785: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +786: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +787: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +788: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +789: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +790: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +791: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +792: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +793: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +794: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +795: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +796: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +797: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +798: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +799: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +800: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +801: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +802: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: new notification grant initializes local notifications +00:39 +803: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:39 +804: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:39 +805: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:39 +806: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:39 +807: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:40 +808: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:40 +809: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:40 +810: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:40 +811: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:41 +812: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:41 +813: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +814: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +815: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +816: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +817: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +818: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +819: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +820: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +821: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +822: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +823: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +824: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +825: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +826: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:42 +827: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:42 +828: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:42 +829: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:42 +830: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:42 +831: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:42 +832: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI bootstrap failure can retry without showing private exception text +00:42 +833: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets manual finish before leave time sends lateness 0 and navigates +00:42 +834: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: reset invalidation disposes the old app while the actual platform future remains pending +00:42 +835: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets manual finish after leave threshold sends positive lateness +00:42 +836: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: ko partial deletion screen is distinct from complete and fits large text +00:42 +837: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue shows live leave countdown for ongoing flow +00:42 +838: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue shows live leave countdown for ongoing flow +00:42 +839: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue shows live leave countdown for early-start flow +00:42 +840: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue keeps overdue timer running after leave time +00:42 +841: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:42 +842: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:42 +843: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:42 +844: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:42 +845: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:43 +846: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:43 +847: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:43 +848: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:43 +849: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:43 +850: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:43 +851: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: uses alarm language for iOS AlarmKit capability +00:43 +852: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: dismiss keeps notification delivery when fallback is available +00:43 +853: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:44 +854: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:44 +855: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:44 +856: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:45 +857: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:45 +858: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:45 +859: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: monthly and end-date controls preserve a valid edited rule +00:45 +860: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:45 +861: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:45 +862: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:45 +863: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:45 +864: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:45 +865: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +866: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +867: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: scope defaults to only this occurrence and supports following scope +00:46 +868: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: scope defaults to only this occurrence and supports following scope +00:46 +869: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: following edit loads the series base and preserves an individual name override +00:46 +870: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:46 +871: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:46 +872: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:46 +873: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:46 +874: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: recovery confirms before deleting and prevents duplicate resets +00:46 +875: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: failed reset remains recoverable and never reports success +00:46 +876: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: partial reset opens recovery instead of reporting completion +00:46 +877: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: privacy keeps all six policy sections scrollable +00:46 +878: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: management detail and end confirmation keep independent preparation +00:47 +879: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: (tearDownAll) +00:47 +879: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +880: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +881: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +882: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +883: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +884: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +885: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +886: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +887: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +888: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +889: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +890: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +891: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +892: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +893: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +894: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +895: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +896: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +897: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +898: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +899: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:47 +900: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: warning overlap message names the previous schedule +00:47 +901: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: error overlap message names the previous schedule +00:47 +902: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:48 +903: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:48 +904: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:48 +905: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:48 +906: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:48 +907: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: fills the bottom safe area while keyboard is hidden +00:48 +908: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:48 +909: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:48 +910: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:49 +911: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:49 +912: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:49 +913: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:49 +914: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +915: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +916: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +917: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +918: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +919: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +920: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +921: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +922: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +923: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +924: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: saves date and time picker selections through the cubit +00:50 +925: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: saves date and time picker selections through the cubit +00:50 +926: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: saves date and time picker selections through the cubit +00:50 +927: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:50 +928: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:50 +929: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:50 +930: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:50 +931: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edited name and place are submitted from form steps +00:51 +932: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edited name and place are submitted from form steps +00:51 +933: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edited name and place are submitted from form steps +00:51 +934: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:51 +935: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:51 +936: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:51 +937: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:52 +938: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:52 +939: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:52 +940: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:52 +941: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_state_event_test.dart: PreparationFormState orders linked entity steps for editing +00:52 +942: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_state_event_test.dart: PreparationFormState validates fields and converts visible steps +00:52 +943: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart +00:53 +943: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +944: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +945: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +946: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +947: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +948: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +949: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +950: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +951: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +952: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +953: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:53 +954: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:53 +955: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation errors explain invalid name and preparation time +00:54 +956: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +00:54 +957: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +00:54 +958: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +00:54 +959: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-generated-policy.log b/plans/audit-2026-09-23/artifacts/c01/c01-generated-policy.log new file mode 100644 index 00000000..01eef09f --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-generated-policy.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Generated Dart policy is aligned: outputs are ignored and untracked. diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-l10n.log b/plans/audit-2026-09-23/artifacts/c01/c01-l10n.log new file mode 100644 index 00000000..53194980 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-l10n.log @@ -0,0 +1,2 @@ +Because l10n.yaml exists, the options defined there will be used instead. +To use the command line arguments, delete the l10n.yaml file in the Flutter project. diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-local-only.log b/plans/audit-2026-09-23/artifacts/c01/c01-local-only.log new file mode 100644 index 00000000..71a9ce0a --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-local-only.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Local-only product boundary verified. diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-manifest.log b/plans/audit-2026-09-23/artifacts/c01/c01-manifest.log new file mode 100644 index 00000000..29c18cf1 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-manifest.log @@ -0,0 +1 @@ +{"manifest": "android/app/src/main/AndroidManifest.xml", "sha256": "6b16f0fbaf8febcc2f9647450e40ca498462e2142f279c90666f86f77f14f210", "failures": []} diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-negative-proof.json b/plans/audit-2026-09-23/artifacts/c01/c01-negative-proof.json new file mode 100644 index 00000000..91232d02 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-negative-proof.json @@ -0,0 +1,14 @@ +{ + "baseline": "3771615dd2b41089330ba74277d85451d4421c4f", + "exit_code": 1, + "before": { + "lib/core/backup/backup_service.dart": "b8afab53af794ad93b384d289fb41960e19ee9574bf28e8513a432e3687ef94f", + "lib/core/database/local_data_operation_gate.dart": "e062fa4994138f825c0643a6831f7b171aba277d1b35d97549f27f50e4445dd4" + }, + "after": { + "lib/core/backup/backup_service.dart": "b8afab53af794ad93b384d289fb41960e19ee9574bf28e8513a432e3687ef94f", + "lib/core/database/local_data_operation_gate.dart": "e062fa4994138f825c0643a6831f7b171aba277d1b35d97549f27f50e4445dd4" + }, + "restored_exactly": true, + "observed": "Expected spareTime 17, actual 0: stale preview overwrote a later durable edit." +} diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-negative.log b/plans/audit-2026-09-23/artifacts/c01/c01-negative.log new file mode 100644 index 00000000..65947e03 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-negative.log @@ -0,0 +1,14 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart +00:00 +0: preview followed by a durable edit never overwrites that edit +00:00 +0 -1: preview followed by a durable edit never overwrites that edit [E] + Expected: <17> + Actual: <0> + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/core/backup/backup_restore_stale_contract_test.dart 31:5 main. + +00:00 +0 -1: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-python.log b/plans/audit-2026-09-23/artifacts/c01/c01-python.log new file mode 100644 index 00000000..1a39f117 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-python.log @@ -0,0 +1,16 @@ +test_boot_permission_must_not_expire_on_newer_android (test_android_notification_manifest.NotificationManifestTest.test_boot_permission_must_not_expire_on_newer_android) ... ok +test_disabled_application_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_disabled_application_is_rejected) ... ok +test_duplicate_receiver_declaration_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_duplicate_receiver_declaration_is_rejected) ... ok +test_exported_disabled_or_renamed_components_are_rejected (test_android_notification_manifest.NotificationManifestTest.test_exported_disabled_or_renamed_components_are_rejected) ... ok +test_invalid_xml_is_reported_as_failure (test_android_notification_manifest.NotificationManifestTest.test_invalid_xml_is_reported_as_failure) ... ok +test_missing_boot_actions_are_individually_rejected (test_android_notification_manifest.NotificationManifestTest.test_missing_boot_actions_are_individually_rejected) ... ok +test_missing_delivery_component_is_rejected_even_if_named_in_comment (test_android_notification_manifest.NotificationManifestTest.test_missing_delivery_component_is_rejected_even_if_named_in_comment) ... ok +test_missing_exported_attribute_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_missing_exported_attribute_is_rejected) ... ok +test_receiver_must_be_direct_child_of_application (test_android_notification_manifest.NotificationManifestTest.test_receiver_must_be_direct_child_of_application) ... ok +test_source_manifest_can_deliver_and_restore_notifications (test_android_notification_manifest.NotificationManifestTest.test_source_manifest_can_deliver_and_restore_notifications) ... ok +test_valid_contract_without_explicit_enabled_is_accepted (test_android_notification_manifest.NotificationManifestTest.test_valid_contract_without_explicit_enabled_is_accepted) ... ok + +---------------------------------------------------------------------- +Ran 11 tests in 0.002s + +OK diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-review-transcript.md b/plans/audit-2026-09-23/artifacts/c01/c01-review-transcript.md new file mode 100644 index 00000000..89dc6e5e --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-review-transcript.md @@ -0,0 +1,29 @@ +# C01 구현 검토 실제 문답 + +기존 grill-with-docs 3문답은 issues/C01.md에 보존되어 있다. 아래는 구현 중 실제 추가 질문/검토와 답변이다. + +## 구현 담당의 writer/claim 의존 조사 + +> C01 적용 경계에서 한 가지 구현 의존을 확인했습니다. 현재 일반 durable writer는 LocalDataOperationGate에 참여하지 않아 gate 내부에서 revision을 읽은 뒤 await 알림 취소 동안 편집이 들어올 수 있습니다. C01에서 generation/revision token과 단일 claim을 추가하고, DB transaction 시작 시 revision을 다시 검사해 durable 교체를 막는 것은 가능합니다. 다만 stale 후에도 이미 실행한 알림 취소를 없던 일로 만들 수 없으므로 A09의 runtime/replacement 순서까지 완전 충족이라고 주장하면 안 됩니다. 권고: C01은 claim 전검사 + DB transaction 안 최종 revision 검사/부분상태를 구현하고, 검증된 staging 및 모든 일반 writer와 replacement fencing은 A09 통합 미완료로 명시합니다. 원본 데이터 stale overwrite는 막고 OS 정리가 이미 발생한 경우 후속 reconcile 필요 사실을 결과에 보존하겠습니다. 이 경계로 진행하겠습니다(새 제품정책이 아니라 기존 AC의 미충족 부분 명시). + +## Root 답변 + +> 이 경계로 진행하되 완전한 C01 stale-before-side-effects AC는 미완료로 명시하세요. 최소한 claim 이전 generation/revision 부적합이면 OS/runtime/data 변경0이어야 하고, claim 이후 일반 writer가 들어온 경우 DB transaction 안 최종 revision 재검사로 원본overwrite 방지. 이미 수행한 OS취소/세대변경은 typed receipt에 남기고 현재 데이터의 명시/기존 reconcile 복구로 이어야 하며 단순 stale=no-op이라고 보고하면 안 됩니다. DB tx 안 nativeawait 금지 유지. 결정적 barrier로 preview→claim 사이 변경과 OS취소대기중일반편집→최종tx rejection 두경로 실제Drift 검증. A09에 모든writer fencing/staging/runtime순서 통합을 명시해 C01닫지않음. 이 실제 구현 의존 질문/권고/답변 원문을 /tmp/c01-review-transcript.md에 보존해주세요. + +## Root retry/receipt 검토 + +> root WIP 리뷰: BackupWorkflow._retry 단일 Future가 receipt/generation 구별 없이 공유되면 이전 restore의 느린 retry 중 새 generation의 다른 receipt 재시도가 이전 결과를 받을 수 있습니다. retry는 operation identity+generation 기준 singleflight로 묶고 과거 receipt가 새 작업의 후속으로 잘못 합류하지 않게 해주세요. 같은 token의 동시 apply 합류와 commit 후 재제출(재적용 금지)의 typed 결과도 별도 테스트. _completed Expando의 과거 pending receipt를 retry 성공 후 다시 읽을 때 pending이 계속 남는지도 확인 필요합니다. 아직 WIP임은 알고 있으며 final 전에 위 경계 확인 부탁합니다. + +> 또한 _restore의 await _backup.apply(input) 뒤 generation getter를 읽어 committedGeneration으로 쓰면, commit 이후 반환 직전에 다른 replacement/reset이 generation을 바꾼 경우 이전 commit을 새 세대 receipt로 잘못 귀속할 수 있습니다. adapter apply가 실제 claim/commit generation을 반환하는 immutable receipt를 사용하고 후속 currentness는 그 receipt 기준으로 검사하는 편이 안전합니다. 에러 catch에서도 '현재 generation'을 이전 부분작업의 generation처럼 기록하지 않도록 시작/claim identity 구별. deterministic after-commit/before-workflow-continuation gate change 회귀 부탁합니다. + +## 구현 담당 답변 + +> 리뷰 2개 반영 중입니다. retry singleflight는 receipt 객체별 Expando로 바꿨고, commit 직후 아직 delivery 대기 중인 동일 token은 completed보다 pending Future를 우선 공유합니다. BackupOperationsPort.apply는 claim이 발급한 정확한 generation을 반환하도록 바꾸며 catch 부분 결과에도 claim generation을 보존합니다. retry 완료 시 약한 참조로 연결된 token의 cached receipt도 갱신해 오래된 pending을 반복 반환하지 않겠습니다. 초기 회귀에서는 비밀번호 dialog 뒤 busy spinner 때문에 pumpAndSettle이 끝나지 않는 UI 상태를 발견해 입력대기와 실제 IO 표시를 분리했습니다. + +## Root UI currentness 검토 + +> 추가 UI currentness 리뷰: _loadFreshness 성공에는 generation 검사지만 catch에는 없고, _export/_restore catch도 _routeCurrent만 검사하므로 같은 route에서 세대 변경 후 늦은 old error가 새 화면에 표시될 수 있습니다. 실패 경로에도 operation 시작/claim receipt의 currentness를 적용해주세요. _resetData confirmation 대기에는 _busy가 false이고 확인 뒤 mounted만 확인하므로 확인화면 중복/대상세대변경 경계도 확인. reset은 성공적 invalidate 자체를 stale로 오인하면 안 되니 destructive acceptance 전 확인토큰과 결과표시소유권을 구별해주세요. 기존root검토와함께 transcript 보존 부탁합니다. + +## 구현 담당 반영 + +freshness/export/preview 실패 경로에 시작 generation 검사를 적용했다. reset은 확인 화면을 열기 전 busy를 획득하고, 확인 직후 route 및 기존 generation을 검사한 다음 명시 reset을 접수한다. 실제 reset 결과는 자체 invalidate를 실패로 오인하지 않는다. old error/freshness/disposed route 회귀를 추가했다. diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-source-freeze.json b/plans/audit-2026-09-23/artifacts/c01/c01-source-freeze.json new file mode 100644 index 00000000..0a128f40 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-source-freeze.json @@ -0,0 +1,29 @@ +{ + "baseline": "3771615dd2b41089330ba74277d85451d4421c4f", + "files": { + "lib/core/backup/backup_service.dart": "b8afab53af794ad93b384d289fb41960e19ee9574bf28e8513a432e3687ef94f", + "lib/core/database/local_data_operation_gate.dart": "0237e006967fa462aee71056e5d8e184b20621d366a62ae4f6df7da7b80842e9", + "lib/core/database/local_reset_protocol.dart": "02a56f3897080b7599e0265f8c15e7c425031467dc0e93025fc7348101ed9e69", + "lib/core/services/alarm_ownership_journal.dart": "d73e238cca1def0492ecd74ba8b677214cc04baed2d3de790d64773ae61a3a1f", + "lib/data/adapters/local_data_workflow_adapters.dart": "10717c376a3cddfcf63abc2c9ff0b7b767f1737f44f2b259cefd42419c9cf837", + "lib/domain/entities/backup_operation.dart": "6720b667d4c67e2bf223d4540b296c176b1618ac67ccb7cb2b7bf2b27e9515e2", + "lib/domain/entities/local_reset_result.dart": "a2c87269003a16bad04ed3ce5f366e319e435c5ad2bb214376e6645f69282bd0", + "lib/domain/ports/local_data_ports.dart": "7b461658630fc11d72546bad76bce2a44ae53d1e1fe052138ac2c32a5f106ecb", + "lib/domain/use-cases/local_data_workflows.dart": "52c90760373f94fdd1e36b8bed48265695d2579dba1226a70092e754cfb60e88", + "lib/l10n/app_en.arb": "cd319e10f65a966611a41a26a0094a0688b5f980f6a229f1b75c45b6cde24bca", + "lib/l10n/app_ko.arb": "8786557fb72d98a19ab968a7d31d7daa030756b2c2b1d51cc75fd8f554645541", + "lib/l10n/app_localizations.dart": "1e5cdf4cd992b26a39f258a6308137f2e7ac22e3066d7f9161878afad192b84c", + "lib/l10n/app_localizations_en.dart": "bcfd79a0d176c7ba105a22e75e734dc6ad896c3255a10827cb52837c6b397d37", + "lib/l10n/app_localizations_ko.dart": "9d8fecc2eb47894cfa0fd619078a1aa5986745f80e6a966457bea85fa5b9859a", + "lib/main.dart": "0316ce499e1b67dbc9938e26bc1e7939582c6237b3cc94700a473ef4cd0f7c8d", + "lib/presentation/my_page/my_data_screen.dart": "5166b981ecf73a7da8278b541a8057da73453ec8784523994e35de1a8a4d4ce4", + "lib/presentation/startup/screens/local_data_recovery_screen.dart": "b2a791cca2339b0df1a8c58c71cdd56084459cb2506d59e196bdd5b135714f1e", + "test/core/backup/backup_restore_stale_contract_test.dart": "9de2084511f0750e70a63a4b51308062940699881f532f305c2fca979e99b328", + "test/core/database/alarm_replacement_integration_test.dart": "6a9bb66138ad8322f5f61d6d63b7efd72d6797c14a6956e252bc11a1b0c9f8b0", + "test/core/database/local_data_operation_gate_test.dart": "3e8d5b1e105a774364b48060abed94d5c2851f9c9b86e95d0d2758f593647ad8", + "test/domain/use-cases/local_data_workflows_test.dart": "8ea450f1479820e9431294fd1df1b8d717752257abf8b0d17369ab7d92b36b79", + "test/presentation/my_page/my_data_export_test.dart": "3f4c2a8090db0d8a4ef96b565a127ec8ca627cf02a1b24584412f3416cb27bb8", + "test/presentation/my_page/my_data_restore_test.dart": "d0f0058415fbc8323da9eaf071c2bba0a793a7cba5be7c135ca2bb2acbf07b2b", + "tool/qa/capture_local_data_workflows.dart": "9230da65ac4af641645da2d46172a39b8b63bc132d9e656c6955adc03009935d" + } +} diff --git a/plans/audit-2026-09-23/artifacts/c01/c01-ui.log b/plans/audit-2026-09-23/artifacts/c01/c01-ui.log new file mode 100644 index 00000000..2606d577 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/c01/c01-ui.log @@ -0,0 +1,3 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/tool/qa/capture_local_data_workflows.dart +00:00 +0: KO EN 200 percent data workflow states +00:01 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/c01/freshness-failed-en.png b/plans/audit-2026-09-23/artifacts/c01/freshness-failed-en.png new file mode 100644 index 0000000000000000000000000000000000000000..a73598bb65e8b7f5d79f65f264f0f2a49643e5e3 GIT binary patch literal 154557 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%0AXfdp&itFHDdlNGq4wZ>MxaCg_e+JeApvAgk} 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 literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/c01/freshness-failed-ko.png b/plans/audit-2026-09-23/artifacts/c01/freshness-failed-ko.png new file mode 100644 index 0000000000000000000000000000000000000000..cb61c83dab26660a95c6784f595ea8818f29532b GIT binary patch literal 103153 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 NZ`VO%`i>Ll{tF`fjvW91 literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/c01/password-en.png b/plans/audit-2026-09-23/artifacts/c01/password-en.png new file mode 100644 index 0000000000000000000000000000000000000000..61677e05360bfa0f1bc71f743c21bfe120f96e7b GIT binary patch literal 124505 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 MB^AYtlh8 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 VW0a3qBam?OKt^6V@6MB#{|B(=KI8xZ literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/c01/preview-en.png b/plans/audit-2026-09-23/artifacts/c01/preview-en.png new file mode 100644 index 0000000000000000000000000000000000000000..14bcdce0d052bbe3fc40a195176c47cc5fcc16d7 GIT binary patch literal 143753 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 literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/c01/preview-ko.png b/plans/audit-2026-09-23/artifacts/c01/preview-ko.png new file mode 100644 index 0000000000000000000000000000000000000000..0ba5bc43e8a28f483977f38d230f3fa72669696d GIT binary patch literal 98830 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

Bs!ncZUcfHft~gX9&QYDL*8G%6XlAT-zr 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

-k+5K9;ku^4sQj&3L-;#K>9{7?f#JX7E>BQ4$;UFa|1o%K-R zlG_*YAJ0IER36aF#>E*FSAYwdUOfy2%4q7SU`LxogvmtnW)u~`7$M22vggOXz^tU# z=O;G_2_31m2l>GZibhy}(>lWisrEpy02;xJRp!_hzL4js zblK931?vDYDGx82&i22CHfnHRmr7;0oetnV{NHC5k474*%_q%j?{gtITnJ+#g|E1b zelU7`f1-ApNdYf5ruFx65Q}24xeRrAgG!8DnuY#?0&_P# zSAKu-HG$!jZR$smSTsB)E_<)5tx?8V21bz8yi~r$4G=w7~IM1i0rT^#lRc3%uz zEAZ5=BB_}9=p~-vW{v)^RNN>Kn|jb;zw6Z(mdg;bv{@T(-Hxy!rD3;h7i;;b-mUgk8a9LCqLC8`(@3txw)I@6iZFZm1z?R^c=Vua$T= zn^t>d;MU|Zmxn!lzn82>9zG2Ts=o?aI%SL0WETsr~r$g5PXQ0lq+g_fY zBJ-s-Xx>6np0$M1wwMFKQ+&?B6_|8UvD?3J|N9g0hhP-%OjP`=)FQwUPNwC!u|rWu zx%eJNW4#FT5SBNu+HvZDnT@W;%Fg%GS}_k&I7#(NUEfC#;1sE`BlR)Ie30BQ+eVRZPQTEk8c31dl|d(Mx}mu<*XG zu{5+%^28DHITK1#IQJOX7lgMwm7Qx^?>>s_NGd=)qGk!Q1+&UR@87(+@>E|7Cj^L0 z;;U_~X7}K_MjI1DWy~IN2*Au%VzIzn!)iJFc8PGr@91PAGtuCa;RYjPrla1-j41tT zk0;jfYa$8chdjo2b4miBhtNhot;x7~;EXD3=VsJvLRx^5s{3LUQEw3iJ?2NVK`!?8s^LF}97 z(W64$h1u9qZ_mYRU*8b^Lc;E?Sl3q2qQ@ZtYG*b<*^9r3pMt%=8E^Y+e20xlMBwhi z6V-5#W3z7Ga`vIN;}8#=+^8-c*p{v7#L?Q{C`^v%-XF2&R#FlainG|`)jNa8Sblf{SU#_HxOBFFgwE0K z?rvxjwRWaXhG7O8zy;ZWXSrX&#lRu?1=}Q>^ZG6R9OQ5_rNAD6?ier)twy^75s{UH z&c@dfi%9Y;Xp0l70s@5R;~{&Xl8K&&NwLtPUs|Og za`fh7kh)v;oTd!;msw15%A6Jeafoo~S|~Bf-oH2O_oZ;rp=K_r!#}Zr?GMF7%8jsWw(lRANU`Lm}Ttkg9Mi9K{9E&tvmrjZ&l`z)Ywax7T9d=!f(8% z&2Ml^-lJ?q7bg3Nxt}57(iX495HKd`@sD&C`Pq$hN;V6_Q`PbP7E zDKD>7_laRV@_xS4ff0B7Qm^Ob<<0T*0cCas!~jg5ZRW!-Y2xK71*4Y_4!*VU1ncObFiYSD%2zUK5NNB!53S}$tk8UJ%7l=cu5e0nT+eX%h*Qk z3NCxeZg-H>ydprCi~OWWzag;2AdfgrmrA16+BC3C~wf~7>j z#j4ITgrr-v;VAzA2|1$pt8E5TiZOi^D$ml;t=Yk;sxMBYY!$k`ziLhR zi|7}Ru##uGRzzlJWgXeTO#1Gjj+6BB)qF)p5t(Eveeni_wQre7Je@Mxx_Y;Y?At7q z?Gr{hyw|0m5Ixeuogy`Jo|PuEq9w-_=+W488C?` z7w;3=f3a#idPc;VN+rUy+kK0Ci|`!v^hQD2q7* z7d#NLAuU`5kE7!(^R$7c>p#hJDD_t3{vzYhdmN)qk8s|la&`v($WP^4{UFOza1;)+ z9ofb@y($kBU91c(Q9p=% zDYW4nI0i{%rtY9|UagznnuwqQ%>gd5Po(G)znTRx56m*QGU-2y7on*Nezjbl&aqPeA&186&o3 zTcS(x+=GE{+f&%R&o&kAe;zKJ!GFxvVZ|uvrd!?!RcYOy-{^P+=t;}m<+kH0LB^^_ zet{DMU>iApTw2CExPe~|h(u5fHzPS*bYJK_N1qdU4^0O^Ek6}Q9+)+Iq>LeCxc^DC zLYN;RvFZ0<<$u+??=sD5+e7VS-R~<>H0EiFmV36s3t#7TDP=3Ekq8GtdEp4Q1W&Z^TmyByLjkRj8 zTrviM`$d8<5nGG)hT-dd)P0?@hJIvY5=wo_XX4AuCC?I&UWUU>CP{JFjne{mYbQr+ znZ_&VU4OYu?!4O`6xW zboBpZXuR=RXt;c?jAXU8PRs&3|NMw&m)Bwr5rn>>hOj3HX5JC4(ZlR+&h_s!I|qD3 z#OEfA)Mo{J&!Hv-!I0E(7=3*`rW4}Szvl9BM_GkTv)=1QMEBnv7M{b1hxP#wN96uM zlA5epz54d6a8bR7^Km;|T;IHAn17bBD|h2A*^L|br2l)E3JjJ0zO zPVZ;T--cnN6#yb+b4Sp-LACV^xJTNLF*q&{V6t^u>j`*h0bwxqd)dywUS%E;K_(XL z9vKE*o#YY2~)d$>Q1J3E8gvxB=CDT;x<7-!h z5Igx&K%?+YF#KABF8c13 z{8X;Sk*<$o`f4r))AYfZ8rfMp-Rv{ATWu>P9G2jx7M}Nq*EN7)MdN_2JuEHnwh0z9 zV%QQgj+kV-I6FfV6<~Wz(;ghH1R@5QgCIrpt3xcTHm;B~SIgOyy|agxBdIc0uN_m z_?kbweiDc4B6$F*9en9T=^W@_<~HqNP;1^Y|6xX?cZAJRuW^8fY0bTVXvC-#u6o4& z?C+QCYi8cjKGd5{Wn2}xkUQt>7Xr=Un#xSQqI`Zk49*bw=o+T&s?@Q;}J4aG9M!VbPuAx+kPXo zBU-akdU=+!#=91j1G{_luU$Slm^LH=oUymusW+9r&{;v~*R>NtQ)HM}So6EMx^~L9 zGN)$%q@E-96MWCCC#EacTsh~V3MU#3R4pC;_C9;h_p$H2y`qiB%8ZPjf-LDDATQ4< zTT=n(;3VSSQmMB4q+>(i)+~~wRxhZ)@hY{$4dps2gOnuKyNU?4hp?`~>n0>G zYs3(yz|;u0c%2T!(9TY@v7nf;O}>A;G4x{kH7t=AoF=at8pw(ud-k+2y#J-KHWkPW zIBh*jno{U=3rAYK3d0T|&IjZW)2|K<4mv}Qk;oU8d9J;@fct`gaU?%ka7be-vZK+f z>WWl>$oPMoI`}1yuyy+aP_&M!gvk&Em40_EfZQyS>)SU3f}oe!VTN-q5D%Fqf#@rM zP%MS3CS<3`#S5-|duvy3t zDz@YwZys)_b6Y9ve7AX+Fl&guu6cb!07h@a9KHmfAAzY0?3(i=i^h6Hr()2N5;T-z z11PfkWHJc+SeGP(i^g<9U0&#QIQ!wl?I+ZMR2;g0VkZ-ER%AH@pUD_&LU9At z2!9i>1NiLu8*LN;_9zd{qpW6}%!1O=k6snih-|=s93!3T=T}=Uh==L|Ex~6NX1Gu$ zy|ShWVrY;xe}q}Q>wQ)xaD4(}3FaXMVf`Kf$rI;#IWppRz;;5mi%dC+)PM`4YZP#A zxh!|;iry`={h)EL9rj?Y1U3UGy|5{HABx|z7cX8k9>qTpuV;GZ@_`Mi7#6){iCF6E zORg^0K>>nd8gdvx)9~h27Rx zeN^%E>6>?EL1_n-&{kI4vD?jABiM7>FZI$50ESz3XhPTqC(tF37mlW}hl{*JfRmA3 zsrnnmMah51c<}9UmYC3n*fMQ1_AGn~3ou$hSg(A*v=xFVYJGb15gJj3%GzwrSzzBt z)pn{4pdq@XyRARP2~G^Y^(awI;Ie>SX{3~p@j64eNaW>LfDo(x2AoZOqeM2B1Y5~p zzedgY?l$l)HmiDJSy(20bP>i=WjIcvydlunS<{|{gU0E*7aXeq#h-KDL8&k7;-GD^ z8lh?kap-ysMYiDf?FQ{l#+}_e8Z{=6N)!2051}E5hezWA#qPmlc@f~dz{w5(bh7}v z!O{CR3goe)qOhFX_Z4`nJPU#}pltwm=yGY}cDaLj)AgavOjMnTJ3vHE=Ufk>Shp*U zfb_x4Sp8#B@BZh&n41i;G5yiXw49ksxVUJpPpPItstRfs**y}4>9~m9k+YC`SL5y< z1P!|yqpRshZVmkwheJp(n)v;jNM3{<;Gm)T3z$VviienG*SM*x8R*p)k$mN8mBaxO#-7+QlF9cJ+M0+RpI)3XM8QAi#tLHluM z}o4W*aO8`M!_OeF?5N5915{vC{2z~;n ze5V1eyTB*I0nLUobV?)?g<>F9!HbuK&c`hv@8w=_mhoojG{{rA9d1s@Qvm&M#y$t- zWjP#sZOHDyok02y09{ZNo(l`pkCr$B+V&UJe!%+y0i%(K?Iy_{h4Pby3qUoL_zw)K znHv0<(J)gZ1?edyl)+~pmfYp;%!3-31|{J;;oenP3ce%(p9C?Go5|>aHUchH z^2L&Fh6WN3*Foc5E;^$h!;(u6a4o^m7qHRs_|H+y_k|G!N#7UndQraBRdCbJ%+WES!OISw|C9&p}!(e84~&WJ}~j2d;$qvy6w&{q;e-*m3AX0X}6v`U0UsLCXi(+2!~I zxoU&c?=~m10vK%8&6Tv&|1b95JetdPeIKUMKt*MU(4awx z%n>SOEMsO8iew%$(?CSVNEC%+o+%hb)Lt03@gh^Vg(}QP2JtQQXTUs|H}m+W4X~|q|L#+)~)~34ja%m zb#M}4SJGJNLDa4N-{1C_$DUQ)Kvlmcvg^Km1ox?}^|x8KCvI^n+HpOjRz)rJpyh6~ zxlti4T`J}mz=f$o(iQQO!@`T|l!ATlC*)s3$EjI9DkLaajlIwgl=D6&Av|43#5R%a zo(~@^2E97ZFrLw(HjT_mP7K5&FvcziS5Q-B9q&#I2FazM_1g4@Uu(#~M;q?|iPj!|i2HbOE&gZt9 zOqpN<7`9Y4-17M1CFFv-DC|%XYWR=hgAkhGC|jRl1F9yXP%x<`L0VZ~!2lp*Rt=Uy z(kepzf;wGJCM@yJcwst_Qm|i$^&0Vj68!w?EdD4&ZTNZe3az`aK*}`O#D%!Xz91tL z{4S(p+mQFFIK^ODn%-%lWt5iHqIhp%4G#|cVuvAW1UyrMcWJmp#6AE6NY2ui*X!kQkNpyxu|6Xaj%$84bLBU%V30BLA~0)6JxFykz6If4F& z7eoGjAA5|!$@~`lbm;8ZA&22Pa>Ur$`huxS;cu{jnsb04-i$O$}xKnh_?yng4M-8VhZ_J zgHuyEPWFz{LVocOa1bK8`z0JrjYY*9TY(5{Ofzffyvzfy##a*bY;7- z&IW!o?svZRU$*$aff@KS7CEu(c51~cL=u2&(v$vI)Pa9A-}GN`0bSyO|M~fV`v`&f z|IQCTRVF;w%L2O2qPPBi(2iTuODUgvF9C|PTbR7?eSDly$w8OcZXky-0Wc4L95Vmp zStux^eL|!29()Zn8pNwwPfTjt?LJ3od8G)bJm5jF2T^2TP@|xMLeSV*G>A^!A2%@F z@82d4$#>jQEe}O$;pIe$H=ki=9wwoMI ztoJHh4b&%kS=JoREf`Tltz?+ih>{ntf5*pz>cZyod(#-vcSM%XtiKLu(qX;I)Jwb; zL_oVV7c2GOtHsO)0QomXOuP5P8yK}-$46dumkH!1;Yf>GQF<)(>g7Y3bP6hir+P4Y zv9~Ej4p`eVf(lV+>s!|zdfn=wyfIAyv+MAC_o{oY!2>7q{(UXChw{C$BO1%lCEYP1 zJ-(HdRk8pY01U}a%E|d_v!SJW><+z`;=&0G(DCXplp{n<`+Z4bn5p4&Loc6tl9fC{s~ep?*AWRf6ld!|Tg}w?2=zU&5&!RB0D)SXD@seTP(xXM z!@j-nNjXieP(P|lwDJ|QE77SJ zybqJ6BnJkXKTR${u(|+d>!f8uHGxXvB&M2pOfyKTgkoVQC~72v0nfH5Kiw71K`=qM zMN%CQp#*c}Ei6LU{rsq)_av|B_Y)*u9Ddz|88cH3q)3QBDb*nk(dmWC~r~X zLGr&D?I;lmNV@+bwt|}JkI&+mB6=VA^g>xR2<-Y6Vxly-K(?X6DNY=MPd#1&;d2^A zcHnBc<48!4C8jwfXsg5l%^qp%#yfs_7@`lwZ|_1cNBT3+J4pj(V`Dfo1qp&L06xXJ zTQi0@2;uJfN`1W`m=xFr0C#O5@l()2U|vSdtS zBpH!N9IwTw9Zn#dK_vPQksk%!w>U42C=s0D1E8H$V{|hE5@wnE95qS}_`%lT|7}>( z!Ipu@1Miw5K9mwDt#5--hx-rAfB;cfkIWj7?2Z0%)B}ZqmM8yjssgTxXz9>yY-*5u zlAJ^o9-#im%ZBQM?}##wL{mXo#Za*gz>+^mS?D=hAx;Zkeublzc=G~8CTwz4fKMQq zghB+3HnKfV62C;BPTh*}z-(iRX&j^2Y_zxvo9uJ8B%xFcSm8|@at`+u1 zC`LhSGXLvI1gju==LC)$LmJ8g{sft@pd&?D8)bQg*jACCZKAk`-ZV?L`|XAcA6DT? zsto`^BP|nUi3OV|gj~2q4nx>XglyFsoD?m;b6E`?^8V*YO_u(b+r|~83(H*qOcd9_ zJ~zQS`UG!#!L;gbG(?0BhR2uIn+<-KG0svOFbqdBcq2!Rl01MInBY-l)P#H<`MVWu)MJVfQb>hBLG`r^`bt| ztdhtIK&}rC2ls2Pln|nplDe9X&O1a~{6=9KBkCIK-U3-i9x&N6XU^F18vGG~nxAm# z>_pxG;vDX&1lSGD3V|>Q!VcMFw$p`T6yvYK=trN**uGvfuWuZ*J?6FRH&fK#{0=A+ zYFY(jNsxun*#E-_@BhYWaT+%n1Vq2oRL)^yRz#e%7(QM^6+$T4kbE{y{Vs)1B5ANU zGYg!#B>EEQ;-`Wuv>*&c8ZM~ax9I_pHDOs1_BF7^g!nr{OsBq7>V=6B?!Z@YY|9KW z<}o5J%Umtph$yKBctJX4!zo+bYkuIZm_r0;dwWw8~59|=_9nR?8N7cMep z)&CEAkZ&D^yd*hDLxUdK2~p)LSOGe@MwFmxQbd|AVAa|rcak7g%!}1Cl%(6EEyc%s z?BVbXhALN-{qk1u7atJ|Fb~p+526Tk7(%h{2fBr#^GA9Z+nwBEXb}6>=;}KH*J2bv zh_jucuR*mK0Km^JKltaOJJkl4UNn15n2WKhLO{kIor@NJNI9paK|!arNR1$Y5V_d5{Y2f}Tf z0!M_*f6%O|ZR%-g24>pM@)005QPk{P)54q3xe2xzq%GIp|vf7$$Q72S0dE;ior}Jf@qEPZ{Bm?!h5VJZ;d( zMoftwytk>}Yk>`o5^>9L@Tnjc3ovmwiozR&@-Ppd=6tw<0UNzXJgSke`2^alYc3Xg zJdE&{B%Yb1pY2?5!WtzAD&B+_mXDjySc^ZDO*)14GV}Q%aJ~VmUGp--=_&XLr-tK_ zQ3IQ5<QXXBiV}I7Ellot2!tVqyF}JVk@l0xFDj!4*c;-Y7KDd_hQ1y;lgR8SK@F0N`jvnvCob}5dU_UZJBY$ zan2gN^zX^6NYRfTFD8M{wJU#S;_M?D37L;HFTlzpbFOQz7EY`#(t_c@Dn5_a^w(7X zZaj!Ux4Q~+m`aP2iaN+2@=Bzvf{(ApW#DApRZEJqOzZOj0yr&C5 z7s&kNQ{mNn)k~p}(c_i@PEbo-l63gC(a>kvIXj%U;PYK`7ymnmOqJ&-RgB)GF6GG6J^hE*>4cH zyQAXvM3Pyd;c@z)EN$sJ)vp3#gCcvlfV% z{-|%eX+F^1!-IHhlOW7=tt{$$b%1l%!Q^B!z>Q1gBiJZG;m$V7S-{$O+|>_f`I;Zb z7xGM~0McYW7q$AN`m5jcELvxF;0@naub7$=C6`vk0=$`$z?RhNMvydX*J=TXyum=+ zsLT7nc*RfeL|aazFL(^3GeZ$Bul3&fTY zuNrc2MKN(fexE;T6Poo*SOEO6%W5t&Gcd(Iq}5HLX$FJ%5`he+YNQcxhsZF%o!6S) z>w$Qa?>!|2O|RET>u~53^C;*kFcmTuc^Lla(dH~I%Q+8_v_wXh5XWGGB?Xc%@m&Kv zMLq?gu+;ODE3k?&|L|-J#vE^)|6;elN##yq1h|-(F_4Fdl?DX~v7#lupR)#KPNUng z*rriKe(ssVF<>?6g3g6}23U@pOAJB)Rg=jsNm+_nl63k`N7N$Vq;~a8;o%TSbJ%jx zaS$mF!5AgNyn{$xfdcO?U!K=DtDnUY{vKIH=%tUIn8j}pO!v#o-;oomry%N4gUy9T zswySGv16FP5x)0dZ#*ZWj45r_JghU)i9f!Lfq}}$$A{F&m~9=q_Tzz3*fxk^aEG_$ zl>H3+zCwJruuiaq5W5B`)kZu5l7URb9aty2O-l8MhvaT}tJeN3=sZ^xhPbI6{fE$E zfRG&FqzqBh(VS3Ro?y4$pr?k==`{&;MT_KtVZBMk1pLXw@%0t|o6+hrHrs=G?+N+% z0cNVRMVQa;06W-tK44GI1{*x-o7Ju8Js<*6JLYB z;ww%ZJC(@iE~~^rmN;5c+QQDCScUv%W7)hO{^M-W0q&Tc<17CyZD)Ql)lLEb-uut! zA&nfv3)2rUk-vl1;NjxR%^k0-7soPkrm;pPijH8!ioZ_pWqWGga|DLKbbk%83PZ4a zP-v!W7?;Omt0OP`vrHoIAwkq%%KZ-NFwyVW!Ex;BAC!I=)5L!1N%@aPvS@K}-o&f3 zB^J(6gpK2QGEawx)P8+l3m1kL*er2D7j`1GZ2D9r)p)ze#yh&{OE^q+$oz5?;%I6*1I>s z8w^DyLc;gzwO}5PR+*83mvK3u*Ey&Nr96 zYV*M@$n4vWm-82KBNTMGaKc|%SwSG?^lKVEJ0E3xSs6jaJ3!>&gDGJdjUd$~ z?EUg`%zYm&6}!zoGs+c)B^Pv}y3@~SC{WB0G!hEr22U@{J&~}>tK@rd4le)`WMn~2 z*EN&X2fl`$cnhjZo#zE}DFx^w1$Ejiiar(FCp8(-P=GM}&&0fBzhUD4RRr~_~URt+sd$fwIiK@B&9G|?d4;;JvSXvwx|j-m;BEKy}*hQ zkrKF=8xfy(3l+9lU{-rQDpT9-jFvSToSv~;c;nN1043#1HU0Nt;Fcc zs-Sn_=g-#=wyZlpZneAxtx`-C8I{gpqw4-qw}4z5B!b7h8o_Lj(Q|i377avUU`tq$ z413hC=;eDYmPoL}L?3~ZpjvukCes2k_NHf#Yieu1;){ta5IfW=BTMX5C}DppKB3)t zv@*f6b6fkfvl!aGwJLzo1K;Q)vpCznzCIH6QC%zl8wKEg?7fMvlHPfIXB>;7)9dGY z{NDVvWzH)SGw;ZjBArkPtpQPSXLKSPij8a4unjJi0vY0NICOzRB_`clBzT_HHyumH zy|!L^@1tCDOj$#SEw&DD2vp9P{zi?8)wEAymRoP;SqK?s1B5pzKd9wy@DAlo$p_PZ z_wggc^Jsy(ifrYYVgxt9Z2rriUhLCV@irbAj5r3_F={Dd=9D;_nm7>1!2KRJ!`Uqm zc~|2f9xTCEHo2)JlSp`Q=Q-LgS3}P3rBpwwN%bKuTqtwZ6JEk>PD?9 zBe?VDf&KfBMt>xg;fD{acaBmQKO#K39#PM4b2bPje`j%g-1zuo)J)i4vz}WP>79FU z^wjIt;q@1!?|HDLx$Cr{Kn*QfCdIK!Zj_37-?|LZ5)3UNx=Qb$<<0^dRLtMx{_`cC zgtq$KUcbV>Nh|{vIJ(QL{oaVX14wdUTl^}MkS#)YBcP} zT^_*SjtCuqOGZQ?oeRLL0zpnLE=6?~ehQ6rjZLtv5uX)Kuw=b%B>aNxR1RXRS?$md zEIuKY6+H1(UAFS_0be+8(8ZiP_&}26tSxVXxCbnV@HT7>D8jDgD8N%B5CjG+wz8&ve1!4bmlqG7mcM($omz!>8DXq=~{oVDB3Av_264eAaD-gZwRl7P4V`XWuvF|>j>45o;~f#^^}3? zR@HTouym|}tCe2x(;@t>G+i_#FfGY{+JZMQ`ssB_3e*-&J@NPN9~^aL6~?Pu+Pphq zkDtqyOiwh65lmGK?nZQqGdlz^M;yMzGR`7rxTmuIDAc>pE%y#Y8@ktxb*(~in#ulA z>EI4WA;J_B8EM{>rdPyOkQ=6Ik*7-ixP9(JymhJX8Vx_`s&geI$L;CR*>4Ud)f35* zPqjP{O@w0WddD?=%4pRue9kkj-@N+g%UTTEpLHLC{2M1=QCB&VD!$LTw=NW{c@GnV zDrKEP7J56e86xhru*t3&@iGhpIB*(P|WERv~|MCm$zibj@}om z+R3?#^pEZYaLokQB`tE+N!C8eqZF+`A?Kg<2N44 z!Il{)^yQ*u6iX3OC~3qa7Mf!FS+;LCahWQ^%jX!$Cn9qH^MDijiQ$q@NO>!rpBfr6 zb)FEl?rI-f!1-GHNRZ3fH-FDZewrq?nS}+Wa~1fz&(7Z8VJx}M5GHKk_1L*)9ndDs zi1<()x^dR$MIyU0zH8~|>;&=qz|=0cG=Q&NPD@*`OOp5c+4q+ONW1iGe>WF}oWZR% zyobVKm2a2D%O1u|A?tYu4lNKnA{OFufZ{bygk*-FSKri2q3gIWkOuk@bf{!h3AH%p z$BJsZBlb&klw-jZE(wAP5);RkS=JTn^j|K(c*A|gD6Y>%wu~Mh&*y2Q{CXOhawU)5 zhWcwMnQU}C2fkJJ`22f7`z<}q?mFAWnYs(Fetpj$U`(|WX@i{l&og@;*HKejnQCuz z9>ch8C!T8!*{Q+(J9b1+nv()d{S)y%D{h|CRT}^v<~-e&^V-O^vYJJvaPC5W_6u-D zJ{8$gz@XRJtq$qK&$?AoU+rLtYB?mdZ%jUAOCD`j_0YQcp1)7O{pHBn{G;HW@44kBdVMIb^fq;F*g94C@Ou_W?jJJ;{W^U`p zZtg!~0&KkA;s~dHQUi~)<;KtII#-pRfBF!lud1W-phHBTicaTe^xrS0c{&f`PKHSB z$6M=LvJ6s1s%paP^W8cV#4z>%!*cG1jNUszK?S;g%9|~PNL;d6L5$l)cTmho;H^OS zJ>nP#5RaH7eSujb2FIvq7zyGt_A$${Q>-7&7@WQhb3Iq^{yx@lvO*C+hV$3~65xjr zasPn5NW$hd{OaXNn3)gvSH<%@yotl)JSSB_s?v+cs6N)yvRf6gkAMX-4c*M9_@zvZ z@AyLk=XLK`ssUY5-@WHY)mWC>d_pBV)(^d%Jzag8%jLpKL*h0Io+^>K7JI3h(u_+u zT|OhaJLAv?E!q=jjSXhAjWRTZX|*)5O`qEtEIY~NpuR~n+OcqXNBVRfOOsh#(EP7} zvj^H4kE_q?$)j|dt$JZn6G$Z7RNP|-0%&}L*~Q9Jxn58(aK^}>{{*$LYS}Jbm2(=q zOd<~D#EEXWxyCD|jrEiW60;br3VQcx&7HLU(lLx7>v=++)0Cwuxts&%C2I}ux8Rk0 zbw4Bmy)!V6kBXN!2vw)7g*)PVsSrZxB`4rLzI4y5&yONy1K)KUToq{89 zQR7VI=A!oO9J7C)tq@C0chJ5I`Kc{b@nc3IT#bufCZN}B1A&{MLdtTAN=h|E$PT?p zH_t}3u!@Qk-oFPnc!HhZsG-G=kr3FHlGDInbnCiQ4-mmWTA-kxnqW zpWP424m+lg?IPGn;BHVe=7O2&kG4UBGdq*%U*56kNY~YjwU^CtP)n{a-lb(subrY! z61FQrM<|gk1brLQ=glAsg2I3ng$ZeHx1iky_w6V+XwmE$f_crSuSo374cw=ZZSZ-Z z#d$*rFXf(J-X0@4({wBf4o=I*;*@K9nqPjr;LIDiZEX3)3-LrcoG(%A$d3*tZ6i7K z!KVs_ER+qV6IT#4k`Em#96ELp2UY5}AqO$M40lO41Cty=LzOOOSs!HkDrk!(juvvF^(6P=Eg^UUfOR(Z&z{$ zHar>E8<^94GP!i-H7@Xs`m%e%04y%zmQ4fZ4}3eVNF@sRhsb)+F`R8rpUIp(diZeN zK#OVe)u{G|vjtXJQ`M(Ai#|qi7)ZbR6Vfcq;Qsy+m20MB-qE&Y9qY4@l>;pJ{?n^_ zUvxt&y0k;dKR$f;K!SrGNqMml89ls~*7bY9QX1v=8N1etd(pSo!sMAu8jy+dk5dD1 zYBDHjE&3w$cltDs{`1Bo!=s-PwK4`KCaj%Lz|7|M>Bp8WgG2LA@YP`sOPu^Nv-40a zN-@~wwQX9vW({@Lz5{u(hy84BcKqWdw$*DGMau)pJ@j_EJE)6wEEA7)gVyz+J1HrN zU=N_JZo-L>SarYjhDv zoh{Be3jrmV%S1EnG%2ddRG5~@f*Bwh14#hBN(A3N=J`9fNhS@Lx1+y$K2RxG9|S^M zXN;5<0)YTm~`?pof74H+^P>JS>dUX-}M|9awus< znx=@BO}tQbB<@~HtLW509a>@1er3ft!@7NVJ|fJ0z)G4jnnP5aoiWJovwjbzfS$l* z-KYg8EaO)a5)KLKD=n=DRx;Qe-m#{Nr`p7{1)jdJURRkfO;q9_(YCcqpDJYN4q$qNsV`4sQ?S68W%kB|t8 zpFMf#NV}XL$s~jK-{@b2^5N5RjC9aR#jM-I%|9mgA9MS;U$96s)!O0U$D(MUq%NmO z_UrJ?1k9R^p2^+z_ZMuD$&-=u!`$HgSXVzx=N&6}Fq6->tnfQ>?AXqWUULky!hLQR z)m`z<58Unb-W~ay=aDh zZ-lDJCJ{YHV__^}L)y&5F75?J9~Ve~`ogM4u6V%dtNQzHs^ezG{=4X*UgDS!K@(!R_bV;N`+0=*n|!L*gDkX%d5|XHC98 z(W$$w;>JFIIgjShQZw>QQtQ#qMlghftEp7qdQ+wsH*IQ4e)P=ip3I+>_dlKUR!U{pYycK3CDvc8@ z^X4>74H0??KLKQHO(q#77~2$=Srw^{k(j3g(L;uq7S8T1Elk#Z`s~>t%JS%6@pwJ& zmFzzS@k&F`+dX_TM+5}gES69&#c-T{PU5QpzF#gk%g{DRwI)WhRNUp$+y{rd z4htVbpKou*+(2+aS3QuEGfJ__&k`vobr*bzqxu7J=k$A_%B1uE`g z?JM^A*Ij;%XNh>}@a-?2T~K>+y&*W)T_;?-#s>JZ!DC|~7Gm`^T=s-u0QC27`pRmv z#+_H|i8`Xs_zyHzgs#wT!v!*p!r^35B+0|mXt5FiD78TCLXic-=JjhkS0W-Jj>01+ zyPsj75T&j;``rR!jts*pt!~iXgYlx-`)t4=&B%I({$;jeK8kz$P*>t1lAB0mU~!(8 zU(4bf^}ta~XHxAMFlIuuYU~8AM+I+}ss-52v10$#>LwmIr~({=Spcw=Ee=0yY&A~U z8V6K~U(O0O;cfESOx655H$Q;AItZ|#7>VO9Q=O@g=4;6`CmAS{CDkaT|Cw6g_k=U7 zGyP}aS*mbqj({B9XVt|!Kn!ax;{!GJ3BJIm633J;ZpH#JMC~$}Ec+<9%V`9&fJQa$ zgvJ927CF*nVuABG2~sa0$(&=IIeaBaArirBcty9<)9?SpA&fX^umYNf-^pQ$fge4z zb7xb$ciO6-<&0^Oraxu_zr zH+&rKxeMA30h7_eTrOXM`+fBHx{l{$dP|UaBd%P$LO|Kp-AacIm-K}X$(VhERBlo( zf=M@`gkPJ{F5+Y)u4%*H{k1ZRbTw(+IB29fF5v~OMI~yjbwmky+8AZ#5lz6jG-a}0 z7Guv8CTqk`0 z0keF1I#otT{|QGR`O0&s>AO5Dj?ch-@LUQMIyE@;--FtPQLPblxH_0Rm9PA<1gt{| zIs{*vDHY0V_P)x9C^pju%x{QnAmh3xCfI&@my@3+I2ZRcp5=Sxi~f$^_6Kfa4ROK4 zLky_pT*za{Lk!26pd=-ldTp zZZ;xcFyO9)B#sD+*R5YK%^Up{n-0MW^01IoqQV&UN;%M#p{m|UO)ZUp5HcSEEl0G^ zbuB^1>dOKC-_&8YV+oTUX3b`R7 zS7+1peTx!r z8awJn?@82t*3ZgN#BGM-k68%gL~kh*%6tjf%mH~l!MrXj*gB3QwYTO?ZK@>s6-_?Q>q0qPyyiDuVW1p z8!0@b2d8FWQAvoO#J7=5NRbtQ2{K`f{>58IMqpIO?~_zb@JlzbhaM-M3}AawO^pMy zGN9rw)H?!%`QR~u@6_$jj?1A8r2_&pE0xn>LB;Jtofnn$3mXk}v-5%HXk*QOdibO3 z+V}hD{-OYKQ>MEta}?ZLa3dNw7l$#q(wEJEOcM3rI*l9cUTIO9Z<2R6GVC;J1yYJy z;!ENq)H3v&?UEY@Xf|%l^OBg$5)v%)cvfK61G7vO@xsYkm37-Meq_++=)gF_1tF`^f0agViToGEn(8Q3XKe z5QqUiO*2z>uJKyOo8?DvP~7CO0Idw&i|{`qrDg0RH~UMEkm<>{h2mSNf3`<)X-0T1 zGZsSJS4v%RED*gW(%{H!57V3Nf8($zDXu761s{DWkd*v1DNq$E&|sen7(ZH(Hw9v#=Rs{m!tsjIBy@0y(^Ns-tH@Ad3OC9;2=<;Tn$ z(b3UgrieWp>aC>n^BX8IZRCq+zVZ4AKm{1*k;ravF>s{v=4+g__z-v6s<1A8(aL$O zeVeBeXSZKK()9)<%}kwbL|6q6l^-i4Zfp?0=C7EXKfX-(6la~^3uBgo+ro-E{n{(S z;2=j`)7vADvC_$)CbRwEK~Bz%#1)kCX-RigTu;YLD0NvzS8U0tqd(UBNj333%?3u}ZSKF<9{=cx4^aJi}}~ zca9ONV*qYE3m)wj!cypl2yNur6l}LQS1ix(>hoT*o(y?Tnq<&V@;n~D8}A|Or?Uvi z#BmsIe@ak(hWW|gm8GfVK}vfF^pAPW^Q&by z{{Xm^tXqImk%c*2n(0R9e{?FmCCRv3_{4bDi@w)5&#b&f)mNIxJ=4y911M(ZgUSj1 zj0;_Zcc(lt@81d4Hf%V2!GpZxP21Pk=ZYSVAAA!tQz3odZ0oezyJh|~jAT``YVq6#Hl)!p{0t| zr4x0l*FciRUnFk0Bqt7c>z!tRTt-JCp@4yz1LENRi2|%ZI1_SU4i$oFbI5pqK-te_ zf~w&vN+z4;dpdQe@`Np}(2RS{d)^27f;{FGT2w)AKyV?>oG@{HaPA-)GdkQisH?&C zi20Cx>dIjy>z9Hpah_6w`u0IBc;7xG&M4}y&f&{}0hJ3+PbvOod=L}WF|p-^Gl>NE>WHD-W>HE3x`0PpWV ze+Xf-$f83Ur!EeR*N+}dp2LWbsQQ1oOOlMfp&=^l?c~73&gD2-PFZOs!hydJLI@$K z;FAVMM>oNkP9sg73TON&Y-54FYh{i9;W-#2zUXsr;F9ElH7l3~(!`LZzk>FaVJJ`_ z@}VH($e5d_>Nf@vk}@86yn%;V!D3xSGEg!VZ-bi40+OG7RzhWyZW!?Tw5>m}(Br7e z71!T_;#-Nxn{cecolBLI9(V7CR)&lpHA6gohdm?6Yj{EkX88gRJPCgRxf)wVLWc!f zV1Jy#N$7W#4`(7!)(*FV;9eLp$)fTC;e2hBq95o`Nz#7QIzrm6WA|TF+Wu#l!oFgvNd?;6@W(y*|9j zb)(MjcBz>p-|n8(eL4+01K*gNV&K^96Jy_Z_dfw0qQotO%+E`_zz2yqL543Sy-}kt z-ni*dh2aiBWU%zlFQn5WOJ%QbF=YbPBb?ZGK*d&gOnlu@pocE%VK4n5E~W0emGxMq z!PDPp%6;{+cq1(FOjIxkaF*I)_Uf*n+U+am z34Q;1`l-2^;$ET%3V7&>Jtr5G(ToY8zvOiUb(Sz&3HP6YOXDQ8m*L31fw>D@m1(da zUB17qMHJ#@rd65%dT=mmQB#m57pLqGw5zIZ5!6vkB>WCF%l)f03V28i@qy9?W543R zNMsm-e;&_Y!w4;;bNrWs3JOruxKH zA;tUPVb*_-6wb&wzB^w3y#*Z9ert#y9+;xksPp`#8i38?Wvhnl?dRn!8iP_`R`>#0 zJb!rABt#w3t*7fE8nTu?d^ZjZi#QEmSxf}UUPjn8uuo^f%hxkS3w{;Aed33RSM=pC zojZ^j&>;g0z_6-+H}0E+)!;%6!#DXT(C{wFke+l_W{0OIA`%RAX$s5DQonE29V`A*cEC4M+37gmRmLe3ELuc;0A$kEL$PaXNO4F!T~)On`%3+PS;^Giy;N~Nu-AAV&4K=s`E$eF^*v-g(1rqwC=%>1I@ z`V$lPk`G&B)He@@MxEiSvo3m~A|}%`;-#`%1lSvLO&_z!wRCrCA4^Z!8op&EI6dFt z@`DP!TTe@0Z}^%c{55MpYyOZVC~m)EBz;!QYx!HG%LMx zomSPOa6WOXt0&@J)X0&&zTZgO5*fCyw$U_x=O=~Gk4l?W??>DzvwuPVdn9(k$0{mt zIAz$JPI|@pd**hR4^|tt23zjhww+VbOZR6FQ&OQ)Fm%KKHyxaE3|j4W_PErj1^)vAgfe$U9D{d_rF91rQ~9w4wi<@dNWW<`@^ zPP26E^`(!qSUIRcEA&-1|999arwx(|C#$mPSM~bnu@ZxZ)yIrcLBr{jOQ0Iqs>tm39G z6)J9Wx@!7l!^HM_O>)2K&lFrg8}ON1B3xOsaN}N?R9S`{Z+|fuW~3^xaol$9IJI}6 z`Cg;ip46+79C{)Vj8>EgX!sS$5SaW4~jK+u4nKsaiKi z2i=`as&;f<^r+5vaQI-zZdPw6suaR4%FuHlXx6$X>+N?b@#7))>7*ZVl!a{DyW?Cy zY4XXp9L!GZnLQgGs3kn2D!QcF|AuN^A?7%S{gi_HBr^c^5t}A5yC;5IL7$ z`682QF8$$`gl9$>LU!~2{B4*?o^u^)LRxR$p5lDw@VVgci)HLSwTE+uRc2(EDZjKI zIPj;i^Yr*x+Ks+piRZI3slw;jR4N4QKTb^_`Kt49B;olJd+Qw(OVqc+BC2%BEs_XY z|1OBXUw{$MZqVmzfm&;*t@p>|UCB>nC9q&yQ-l6&B!WU?Oc8GcdYj!18a~U=<9v=v zu%nsmg-C?cd4g5F4SUh|t?pJ`mrA)8CfAB>pQ_(R|K(-sSz8K$0@JVtN)=j~_U4QC z+OwFIO8P_lrfu6Q&aMA_pZ~Os_M@}kx14;gyuN^Wx}74{L}E%dC3-LSp%>yTS)-?a zGFWN961s2lS?l{Rfy?6*K~&SKYb~Ov5S?PjOyNt1+%PsJ_~Ibaye+PQtGc&XCesL*@Mjx~j5?O8U`P3dO- z2V-m5HBPt4C#k&TmfGVn@N&)diK$dG%^fTs^W!rTL@DxoK6~eczMx{`*rH*#<(@ z;}NmldTY1;14g3F)W?)K6+ z960rtMchftZ(QN|DsL0Z&X}m>Nr|u^#-+pBA8*bpJubgJAE&PC#Ya&rH@+&7c=JWd zgh<9UOZ%Pm!dk8e+jRIBjxk(YZ!FU>-|f*Sp3d3je`V%xw{EBMiU*&e_LsNT zVm7^Xk}C0c6gz46uSc;xZJw|Iy6TyMaO6sj$jG1Cj0-7W?GF(J|C70bA5 zt3GhCN>sZ9k{jzcPW?kFeo3S4tD7WChd_ejfl=C*wS1h#oqcXvKP5}dnMZrJw|o?( zy{CISPR{y-reBAU$R|Gj9>t9--Jp85@YW7e6@v>3`Q7O`JO2mbhf99N+J zaXjr!UC>}>3wfe%S3Q)L7IpBD*@LB(uFcWyy8GqNE*dc(rM-I0M>7wW7M}@G_UsR~ ze3;2@kaM8kKJaVl_SffIi^_85oLd~5)}D;Mzn&uGv8Z}iNBd@Gb-~DE&ZGU-*0DRc z8P*1fcU(B%!?9N;q@p=>x>I+hqGn>rA!9u6qMT%WhUJ-({I>FfM2n>fN!tm{$cgF# z*OdvX9JZvxF4@0V&Zn2{>3rTGN?+{xwg1Yk8*#tP=`DPBAK9<0$c)YXT<_Q*rS|Q$ zS=|0LC7zqej4kUX zhw3jNPaVG{cs8DI`{` z-h2(J6q6A(2AloeCnQBC5;k+2IC9^cx6YBfyf&#?^S6AfgI1~g^xf?nS~$s9@0-}% zQxo~7MLgkN}T{IzC|s<*usR@i1MmEDDC1*Ok#jAO02pkTfm&rWA4C6-&2KHocq zW!xow#$lCp*qmeU0r*MSJmZm5UU=m+8eG!4?ytRk@QJNrF47; z{(YUZpOdaY?`!0RgZ=j|b!}I<8`NCKy>^mSS4<$YJK^menr3Tt(J9SKvxdTb%rO@( zUk!iboo%75a#3sWm+67T<7H_yrhi zIH%2ML@ny{slbdGiTpH}U<%qGW)6C@;a_s+9Yn-~syWrF{6Y)Ac>7KD^re$}Bl1IBvS=CxG_fhT4 z)^%3BIoEV^qo%OuOX&|D7U#=-%C%{t`j)>y9}Z?l>*>nY+3YEkt}^Z5{$tc4k?WC9a>kA)w`9q;?dqwq6OTsEB|NHn zQhjWnfp$D!kf_B2KbEJ(Zu%js-XlY*fpj@FyM#Y^+*G{z^42do;JZN^LQ1Fi9`BD@ zMjm3t`DR(8 zDTNvTisu9CvkN;SOupJ&rA?>m_1aS#-5?p~B`>m^fA&b)6H25EO^nUGd2KneHD1l6 z|L}_c#H}kD27a=SCOW;6Q>w3bZSbb~Xfe&1QT$Dlj&MjznBx;E85(9JPfbca8{~R1 zZq=XNS)BQzF|Iw;ey1+E<79kECjF5=8rxNu_OK;0Mnp6(zm!(w zPr8%SBdAi+VR?u8&B}qS7xpI)N#cxjb=P?^^G-6JHEwgv=gJ!&xF)Gx-|f=f*>QFQ z)z|X(Clp&)or@jL-*|PXrs4Su3#(EmTbn!jBN@8FE?E)zhK#OPuh;6H?BWnpHc%DF zV78Fp`QFpBC-!5(h%a@o{w`L%TmB<^Yr8+(3_H27v3RWWXoBV?j(T_9_((SupZ%w? zOtw$G%Ucy%EmkIX*~wYh2W|a*mur*W{mOzoUvOQ|?lUiSJp?V@(?^X`qo; zIIdT4Ay|9E(9uH=J=`h2E)5;+f3e`J=(MF+xoJzPTFt!MlR4+fb;}}3cLP$dNDf*G zG~_xJ{ANu$#rIt;`|iZ0w)>9D@3JH(ES6bHCS?5_N=9yt zmTQd}49Ajpc=ledXQK*FRCeB>Cn}J5_XQP85fzJYSk_u}Mq#Mwb}2Y0q}|hB$Y{;g z5}6ftv0i$t`KWutm9{^6?~9_?yBu(n9GI%S9~`c(t`+EU+fzHWJ=?E(B=UP(+jZ(k zY;S#wcj4WUXRS3Up*V9k_sY<_+`pnLciO5;N8djUnV1Vg-9Q)pr1Ilhu569i^U;y! zIt{+^vU+jvSx&g$+I;Nlk|WOOR?b4ZTek!L$QIgK&^2t0rfJ=cCLMw1Sqe&>TBk_th;)kZQ?-D1?$i60!FI0h%lwu zvYVZmy5{~vB7HFZsQci}?bx)aMvMp zTzZ$EsGi#yb}QO_^bAcDUKZ8l#JQ<;iw{c}H%PkFtOz|CS8&L7Q=Fq>zZMuYue8Or zq#}g-XgZV0>q9&8i)r2&l+-OOkNpzJ{BvtH!F}w3m6N3@-GxJPY40Wqe;YZ8EX);n zr|3B)Xsf%rc571X?AG=l=yejgmE|eb$M|bJRjz;DGbns_ZeHrotmB>|ZryfkWf$rO zCVf*4s_uHT&3*3-pW5jbTHPCYdp-em-s4vhet(2BZ)IK4Q55`wp1;7jLyKrKZT) z(tPTkc-z^~YZf+N-0dWt)$U}nUemX~U*P2Wcz%8A#Id3-A^s<_nlmCZ5pSH1yQsBw z`#YE(`CZW`o_&>OQHY)Kmqi0pz(oaBI~P5=K^x95T*LX!0)MZa48ACKC{jwh^V(@o z;TuybQopI#I#U^gq$!l zdP@1z#Pjj^?&N5pTCR(Brh;9!gEkair8RYZaCn7kx>0lHSME2dbDvAzXXsIFo=BIg z)*BqD8EsI!EW!KMr&wLu%Fv*ETd#BK`Q8=fSa$2*>;}3r$8l-dPOrlyov*bpX=Yr1 zJ}_8SdE!8!y0wDsZrS%_vB_u}PxDV_r z2Gd+ojfsc3HZ%upIJRTMko;JcNo(qnoqd|2E%!h126^xs-jLH3c)=wznIoKJd}Ji+ zi$Pi$^IpxyM9L0aV0+Hq3F8U9ef>=heT&9^#wR1=xhK@+QyAB0?_>Ea9@Df8lxm>S zbmpMrdm8uB-OqkrZ7;cc+2!;M`+ccLo6N3(2F^ev9 zyhU&qanXn&Rj%LeGmVVQ>{lp5^Dh_Q<9gvCoV#z529nfj)LyZq0i0BCp?w!luyiwoR@v<9Gmpb?Ss3l{~8wU zjPc3<3W~^uyeEbM3WXLAR9Upj#})q8tbde{_?S8C;m(<8#J5n`{oQ?l%0Py55e-E4 zxmC_r=L6f?hs__;#+^^lA5WRuFjTKsJaR+q=8FPl+v^vu2GwZd(TNKQSB9&~r%~vM z2$?Lib;nVK9{#fL06mofZ~oG;i<*b8q#tS%tV52IqvP7nF4ZzUVG-=fcU*e8zYNC7 z^HSNi3}B;jIgs6G#T*jPRPpAaKSRI&93J$v%j=$eH0L4L8|JS}M~;A+Y3stpdv^To z`qmBU1={rQTzPiHxvCi4Iuw_SLrVL`W_E^#m=f9a&9d|g9csIqBL$Nb))%FJdsJ}X zl~<~6(~O6Ziuk@{`Fjz8YqGZwHVuf0$VoLa8l8~qI_rEWr&=S1!+BrcM#fFfzHje{ zHhP-A;xX_26j0IJwX%zCNv@8~ZiZeryy1qPa&h=e#U#~xr(dNny2?(oU)eNuSork8 zF8k|c5o667$tIJ7pG>GEvkx7nvyt1oesJeK=1|Gg zw)yQ~_YZlkw(bp4+m%P(57afgo|F-k-!x>X^3wMzN)?Mh`c&7*!#gf`878mujC-W_ z_Cw{0qY`D%E@!8gTw8|4LKfMs>_2x?%Kp?}$|q-OmeRu+7_ZE&pKaXMzRS0wb7|C5 z$@IxR>a=g?Ij@h11$qZG1?|b6;XMCVc=sI_w>2(_qTilXrI@|5<5OB`SGl??nR9gF zwCZ)oH8Ol#e<`XCzYd=qF9;;@M zUZXUvs()rxt8)j#My52`y5l!Wjm>A+8D{L%>%)_-=1Uw{)E5zWx!*O5?ec4>aKDVR z-D_>^!>e1f|19dLrj;#G8IW`PN?q6rr%UU6`>!6VpsD1BV#XJIN{a4Fs&5+-UYyvQ zWEL1{f?_hS)ypZ-^vmpM;NqHUF>&hR_VgYulkTmb;x#;f+a{Y=ItPEfk*e-iaCSH; z>P*b%!HV-u{lfjp&smcP=(WW|8q04N-K}x2^YavQXXfn>`zl?g?qo>k#!^J?|Q8 zMg=9`)7zrVbXh5xTFRml!%!2P2u=NAc-!}HP}c_OfSqIg*91ofe~hOM*75Tx3cZdx z%eyu)_Rfe8yF`}n`-vqkIp^2n9ZIRHA!~RICH%G-%o$Bowcgs&>=yf`BwyzKc8ZaW zXN-@#soi02kU0FwVY|idjeAW}i=4wL)!c=`aw`W$8>p>5AL`RAx*E#T(jn}?#~YKg zKkCboQCG(J^82YLEnfC;ndeg++pn(2PSGW)-FRq@K~N$sIy=2kVMkLCUwlPy)>yQR zF?wc^spMa$t|ZRipxbwQ4KLHo;Y;C>e>$Jtq3Wj1D0WY{wp%;FC$i94CvZt(&CeX_ zAs(D7z73*Qp&B&@bT$|)u)iyzHfv9pP10A7Tww6gbLij{ux%;i{^-x~JZgaRSM@y` zEghS_`{#3q*0f8}NxxB5H0B>j&NEWIQohHcWfMchl)};nH>MA6>wJT+th4Ap9Hp6a zwdcspY~rP-37?v|4`;hFE<1Is)!Zsq@MNy&q{8eVOUl(>W#?b5Ba)+wx`z0T( zlwgl{&5qv&EbV){#6n78!arY0wm2~SV3E@AuVKs!ZEtqRt{!{(dUdY##*0l0gQ;Qk zy8Qu@)`7!jqhFOMPo9$CP`%^wt$69=Rv+uhDLo$aC4)4tgwm82lhtQ5b~X%hZn36~ zXScd?zBM$jEkMl?=4BhTiof@IkF=VU z#lECbT{^jO+i6N|{y{*dwjPq@{rmf@-Nz!fPdsE%OX9kCx?a$J$@qR_Hg&+8#WEpA zr+}kIEt|S&dF?l?t~WTG=CHpnZ~3Gq5ZJ1y(P8c=(b}~gS*T_HFZSLlD39lh0)!AC zxVt35-Cctd+}%C6JHcIo1qf~dg1fsD+}#sgLvY(Bzx~&4)mH7}KJ7eE)W^rn^i21a zbIt}XD4~&nv?@`rf(Q&Z7Ll9GQL8>#go#K;=g;K z4(^lDki`gibrX;|dauId)Gv`q_>BytAjz^vqkFj&tZ#;%wVXf z>O@&GVT(J)sC1R!qIOSzCQON@e<{>uPxD{Cj(k!ZZb@Q1Hp=a(Z94;?<4~mi0QB>XMaLmk&pA?e>w;3wjN+-jH^$_>+k%9`d z2sxotUMd?$@8v$aoIri;O5s(Pmob3G|Hc51kAh$c8?LW>{!wgsxQo}f2n0*Vq=eG6 zyPAm2`TxCeCHTmkiFX+4UwI%FDy5^Bgcfu6X&mt;I9B`~Nu0a^QpERBkyK3j`)fT5 zLr~Lx$B;1E&%(H&FCXm5K(r=-urBJFHe0w&VIhp zDiY*BXYKG|dui)}5ft4>ALB0CO)|HjY`UX$oWjGb&PMWNTS=4ILVqSD-aCR+rux?D zPwtQ0U*}<)?#=7q0swngw7*7$HFG^r`nab{d zm%j0>L6d*t$UZb`2+>+lhiz6UqWPGS9+>4`fkY(@nIQ;gE1S+A(p$c{|6&sL+(5~n z5n9lHZ7OhdZyZOnh~FTy<)wCPD^65+&2mw(XfEqp^-CD{S?$npHN&1bCeFA~vfxbs zWL5BSXW@<;;GK#|#&K#SrKJtfhuBmak1bHc2Sw)*JmD8 zsuimoJ-5l$95japYYBF@w}TEKGSlL$dWPiMu0m{y2^Xvs)IJL^C+d$pk5O0Va}Wy8 zZyB=-T+KOVgnJo(!XD1C?Pn@6AQ0}J zc>$S^DA9?UMaK9iQ_dVTUwJX?Gi6b-`HKx2Ah3trIJhm<>7wI7PqN!V$>bVm<_!BiBSwB*geZT) z3vq&$>RX;%%KiEUJ)3c(Gbjf8<#LNXqLN-$Bk4c6H(zWI-O&2asUJ0a3pjB*@*$WWEC z#}c{Qj)muTO=Rfs7V#hVJxhEL&^wvBYViWL%7Li41ltM3z7s7(>(So8Unp^Twl&wY zXspHt(>;g91ImJ+|^CrIgx1T|9pJvk%w&_N`$gEHEYSz>J`9IypBzGy&0Y&*R?}t1!MYU1d_Y z;CTR#-Td`pB)rw8=6U8gNqV(zH)H?x8OE=?Y~ebRgYesHN(QQ`BXyKs%8N&z)577( zV?TTV*)@J%|G$kswdXgC7CXzW{OQw&|0gcS zlHQan>W%J;s{B13?>J2gr8`TO9mDb3pAf;fYE_`OH35#hPgmYz1{?9PcwptL_pv1? zJE`^VxXR2T4C$T;WC!3m;>CiN7&8ugYCV6e^gO&lZMrr?`m4()9bHn6BXmJ?MjiEy zZWsBWdFEWr6hJoU6tVHwg4;uEf|N022S`W6KZn|WcJRM zW}RSnFrQ~tns17lTW&JFbv|QZVBL~`f6Wb)dn(I$+>5@CkDg6wOcZ7icQ^_lqJOef zW6x1`f>Lif31H}_O;_S*{}OHe1{6WTgWC{50oXjY)`z~H46q1o{w>1C1Y45a%y6%* z3p>~u9n$x@TKL(Q^r_-avKV=~x4+p-QpsRHk&E>x?DHoPkr3(+CTdKBYv5<5|bH!z6bRS#_mHNxyB9w5}g--Q&p$h`3&Q<~eiDD_

iV8R=mA$YHge8l)=$B(BclVKo5 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

-_W59Rg-7wPwR|9v>&9+W-H4g*ii}26HG>e)D|`lM z=uUDj5E-gtK*jP)OCvBQZArANyZcs#)-qqYx_}k%O@2T$aaYKE-aVi1))Riy-@U7D zLu~9q;avFUX5{?V);Ulhjqb$o*Q0s92>Y`&F6G;c@cDe+Aoz7^QIVcjn<1o$ZoGwe z+!G7v=uuj)#B>n&J+rFC8=|V%O5dgX>D<5lrCts`Bc?XD)uF*s7L zV;%K+z29ROr}DFVn@__N=QusOfJS+eeA}$0Z%2x`6OXWt0A(91KEoA`PmPvv>+XvH zK6E(74UYz#HCMZ8N6hIR>!(}p+1^bV{%VoVD4bI338aCL;v4Qr@<#TrbFm^e9-C*4j>-8}Y|+>7RlHX8x@Y7_i3yQ2VPtey?Qd7LUu-!P z|K8)W+@H4XB5d1}_wO_E+U?}UpFezDy*p>rqJgno;(Bemp}9n)_kD+KOdV^rTZmyl zm#eSIYzjqB39IFYSD!SV;CUb3(DkaVO`Os?=>*LNmZ#>pznN_vcW<4Vc-_x?@N5y* z1j$13i%W@5)+cuZ;qV>fE@Vw-$*rwawG(PARQwoiyU7xuW%1d`G%x<ry*b8CQGos@0w7*$ufr?cy|0tFi*u-hs@DQNE(@Ww9a0oc}GhX~4w)o8>73s-}QP zsq0bM)oJVYQaNEZk3a>T1(|B4xIN%Rbx-Ays`BdO2%Cq=m#SSMH_1y!Bn|9O|FOQf%a1*= zBe6Z*pMIn7We3ZCV48fmrrOuM{KUtj>pEfSkxgxPX5NNl|Jd~X2*Pv=_>}ZfvzdO5 zZVz)R%FCmmUZza3ZhIJrDk_#>*qJr0ZSY$ejb^a+@qGxod1F~GWS2IaWK~*77JNCR zBkSD$qKW$e;ai`pSY*h=BmiIF@%8PE4e#Ei{}D5=NAu6kRH}?H#K|R%#iYkle2dJC zA#8L_tppm<$Kdk`G^=Z7EMt)%CY*5N#x4;XtY|z%*;g(CQToA4rxT8GB6k0r_K!>M z(F+7>u0&GyvQ}fUx@d!mHir{bySN!WMq*UKcqmsL9nFdCIc#EXE4-Wp1#_&5(GI<_ zr%t^iGWX|(WQ`^qOL(?|!;EQZZTgM6j&XpRs+|$3_S6C`^ zv=jPsM!zq$!yj$eVT|a?Bbh#xF8}-E!a#IfyVBNm8S3j-{)iB8ILh6hA+5hYqeQYU zXn?YmHP&@cX#OuuK7>8^imi30T4GlZANTMeQN$Kryjd7;bw?ti#x6TeSB;5^rT&Tf z7aMJBRKFP}juCj$hDQmp!>ryVF1dQSir3AR;1(NSz2bWKBd*4)yv^Y(Kyb$E(aYn{ zmM=%!tr@ugwZ5~L>Xd5`u5)Hd*z64_WLAZ$!zRH?4c9D00 zKzJvvR9U z0-sKWnWZVKdo$GvZp7Zq1!>nC_qphqwH~au7zG8OrZr8avgovS;tCj~0p;5ilo-E&AO*)TMFh|$qDjzy_ zOxUZltO~yF?&+BKgeevs2%Q>f4eATeX}m z%F(bvZ-GFVK^kuP_RZya3X1HeE`*D+2ok@3z0mcY;5%o(4-3M_qdE>UGjh2hs6ngw z1EHO&L3MiDaGK(KA4929mEXO*dhlT?omV5&UBG;s^(g|yKjrTevv*GDUqMKt8p6)! z@#>F0XmltKGq@~L@i!6adwrM!THVz&aJZ-)_$x$mVZ*a$Zl0c^KnGxW@%*aK<2{H+ zDQj*)rXednJ|z2m?LiW-7DNg72MjntH*-_SY}m9}g5KJw4M%QFa?x4H@-T2jcS<4> z;A5yjBOyM4N&uicixxlI@PS>mJp`bfZif_4kCagc;ts*>dyNi!NN5d~(wJtf&dxB3 zuRpXUC}b2HlE0m)|c*KCp(ul)M;;c!B%`$VRH-%%>f z{>5{DId$fWkYq8?+lU(7hHNt`wK+1kC-WU-qGV&{FDNqhcZAG|5lJTDHxHn+_brN4 zZ+?S91~aRfm|3_C8D-sQnV?)-r$Qs)YzP@CCN`Vs@r^D8`xPHJBaKU?7i626$yDPmaW)`M2++i*UR03Swj08#=f43-5XEx(G z+9i&>tCif5^)$&!1;%#W=TdNR0vL`N%ORXkB(T5v+*&rb#V>aYCyIc^+RsNP&fc7R zCLv)xb|BtV=SBT3J(QS5z}jI?C%%f@7K4hojnH)$+uoE}Exs6%q$`#bqBPzqfN;qB zMW=K_4`1-!K^e*6-U+iRdXe~__U>fYnZB~2R{$!m2#8)8ilvSW!SJB83ipRweFbroWI{y^L}tq?(DiXJJ!(A(XEp@cS@d) zZkZ+>-Kuk|SK@Ct2fwJ}Kg+F8NGYzykMnBX`}lo{wfxx=bO{wZd+F$Q(Mg>;u4o@T z(CXw|IkC7nBAODgj#>2R=bxK}r;b}uE}h$biS5LBxd=su-dDk|eS)nhrx;3pEAGB? zU!rt3ht-Q}A?A;TLi+iVc_Svdj86IOPWf#fgSNSvn#Ps&{S(d-LMgdJL-k`5&fO)& z#l`D!e!7C*(QGRD16^_SYFsWI-R1R5naF>)-6>c>{=0Iw2I(P2=(xppVHX7D(%=4AuGm{d+)@+)I&& zd+V43HS={#Hl`VV`!Fn`H8}kJqvPC2k!(|jY2$14G~=v@$T*oGA=yowa)*r;$0{Ya z?AX!g7ijv_&o44i&?GR`uCGR_I_iR*v@C}GpmIWh{B)g$MYMi-V88;tSl+6)J;rH@ z#;u-e7%91I6~hfXHGJ?`!c&u_OjcfxAFri0kq2g1BB-#&qCL;7+CjMY^IU$^#Xu8* zzOmfvo%c3w+GNvP#m>&b!F}|o>h9gU*Dx^n<>$Ml&s6wwdTR}m$4&hiSg@l0sN?us zKZ;h&KbtsI#SDf!-aB>&7%1jBJ4AWCRO)#6__5+xclk?^Zb=q#N5v~wuH2ho#FvLU zFS+*@Ki&KA(IWw)L@jFi8YW6`eBN01L|mh9Pi1I*(zT-CD*wd9#2tqXFISnT_b^NwW)6`IV z=wLyYkz9ya^yFZ(Xhh;!HkTUH4y)%Fz1XwftWEiD`C9cZBcGXO1~b|ZJ58EC=T_ow zwz2O_y?~?rG-b7EF4`3G{`>D;v!~9UjkO(Yvh|1;og8RV^xoSLTg>(ZC-f=g$XwZ_u}Fo!r8v0Q-LtY> zcdm_g2`twC>g@E@)3BxeGHcJ%3KBBocZxgd!LZ|~<>koeS1Jjb-=;@8(~N7meaGq( zv|_cWw$Vn_k!oK3`=vvxIYXUa`|)b%Qx{*odSyCA=3emKg03stuD|uDOi%Z#zI%0L zLe(cYIIiNQXpp#&^R(r}P<#8n)p%;^0{Ueaap1#_pAw^;o2@XzZ^f1{l)uVzw*jk>`0$gXv6ZmuR# zN0LR-Ra4sUK*b!tvG8|;h1=;=3mvx@g!)WSdCjM{Da-n^S;E>)E~ z)n=k1xnaWw{bswtCcOy}3(ahD!mLg1^tM4FHZf8fm_D0#?le`A3=0c0u6g@ZYPPR7 zHbv>rWpSxSd@XIswviUKqoww?WM&@qP0w*i@lUd7$-I)Di}}GYQJ;DJ+F(P{UWMjw zZ_YmV^W%_|l=PTM{Zu8PX;K&WEm|RJCv7UiU%P6wtCTFV*q#6l!Hu{E;h?iCC>lb| zHqWwNU7zSvLRH~V&@eTPD>X?u>*$!>lwmQHK9Z=DZ=Rle?&%(e34`8j48u`zOSNR@ z-&UwUE#Ld8c0&m-MeU4xt0Ls$>2cJy$~Q~>V_ha{Rc88P6I-8DLRYqmntnCMqjTAk=L;8&&05&CtQ$~6>Mf*zrN$5+ub!=_?ts~e6Eh{ zkhWtM@1pF(cPu?{!F%QG^J8O`&qm2cVzZOgWrAS`HuAhb-q&PR)o{ieCeyx{5_3wmz8J`#G zw($EvrIfVvUecO=^0Nv?d@bg>(jB=uZ?efG`$n&Pq~xOZ>y|BP4x8NE+_oH0{TeRL z&tDa`fVF+CN*VPFrCp@v&qJ3InBoXWG z$FB1eD!gjRr-pK;LLP4Ab+kLQO2hxILAH9Gvx|$Me%ZE^A`ZD=!|T>RMdjt?$A9;P z28rv_ejmi43N##Ql860Hl+#+NHa|Z7BUKfzk@4)=vv8C8gkH?`hhAQFWdQ=blMW+4 zPDj;rM7#4yNND#|yi~YxBbA&tcf3ma;r;th_Whooei6JlBy?7?Ju1{y{o}oL0!E=n zEJD8c@y4Pi^XN!aTO_8Y9wt@0-FeKj(KXcm$3L;1`W_qHJTlB%tGY`4ZE%v$g@t`J z(F$MCTO7xKeIHN4Rd%>w*ww12b`~Cq;%KA_CWo3zd^w*VvCxi)jO1o#KOG$%ZMRHc zUfw4`J2&PL6JH-Lx+(Meo05r_A~xBxi`}$7y(zhN7Id9J3771d@!o4z10NYBYu?{p zC1}}ktkt?we9f9Q@>j3Et_Ts2#kiZ)#wh8}2ICGeq{a#^Uhl?*fe89>V2;5f7Hu`< zK}Uub#KUp^Lx(PnjEtavRe%5Jk)~h1BgF-)zjmLcqp{&CJg)0Gdo1;eblm2_i-A@y4-`yuTXglkR_f1}VY0#VjOUK%(~q{wC43dSy>cDtT)|B}m}8qa zZv0Urf$58p=-W=&E5sCn`lMhy?C0sdY2!vsA?5eCRtTbJMU4yUec6l!A{xPW;mL~^ zzi6bIbSx3;WAIOj)D)lYxNTe)7x3jdPt@?RWoxz#PqUzX>5X>{4Gq*qx7kTE-_Gu) zvLwOFetbIG`SX)Oj*|mMlX5D`cqVU9gJX0{d~9`0_*Cw7Pfy!F=hJC3>s*FuBw(-D zk=(Vy2x~$KSZE8cx*|&E8hUyKRn->{H*;A{T?jjIH(2|2RK64u&u!dh>TQV$kFbHxe)!fQ)FG`EZ zp!T2?Ow=e`&~xu+`W4@i+bug+8$P#vOh&@e58oYq`xkHYo%FZa>UAYJHi*|dL-rIcgJ2dwJ=Oj zi1IrBY=7A6(~r3K?>`4X2oR)<5_9YJZGp5ED^}=HB{0rE#aOpw;tXpw40ll#dV}Y$ zW}%MVgUg&xx~aYQ3A+N?b);E8E(O2 zrqYX`zZ;h}kVX0iSZyV0Q>$IOOZHIS?AqRTjVKo&hg~R$N^3AL=yq}bZ;*G1pgWI+ zcC&<%bWpWpXJnd$goIjpS9m3X@56_)^wp*Th!hzmrY}dPS4Buu<5^Ygr@wnl6f9%Z zH6FVo>w~l0Y}r%4JRwBfDVw_7zyI4C532OR<#t^-LI*mnHTC?9-V zGOwS+!r&OZOV6Zy@!}H=V}8^hABs+%X%*|njjwOsq|2yz_8grPt2i)~iBDTIFE0;J zTiKu@_>d9SL?{ORyPqQtd-=eBhz8ePa+?1OiO2uBl)(E{Z5q&-Y(CLFxiZeQko4=v@yF zeCT5r;k%Z08_pcH9e9B{n1I@2Tp1$%B`lN!N9cGy`m^MX2dm?`!KO3?4+a*(aP0hb z@9$*F1ha_BXm`~BZqIJ+9vVsn!Rvd!icK+qUr+J%sehgyP?Kku_EWrk`ISaLA*Jx#09`_kXsWd8kaVUFn zVYXH4wZhN)1%g&T&*kD*pYSJ7*00-f_?)=Y!s>QS(~wL*@=Nhg;3|w2hPsUEm~XgT{=!_Y3XX* zK&t)7RdkO&02p}ym{u)6tsHJkAc*sfV#7C4x8hLy`cf(-tnH-8_ts7pGF$vA<;z!m zy61E%8aWzDQ!9I8naR@n+E^8(d{-A1VXIRU{q^+#a6kL|`)SGKd)o%^XpGUHY=_$7 zW0Yczr)aI*AZOIlA96+o-Isw zGLjEvShU;PsR@ldEFk@Ve!A0}Tk-9at$f;XU%y_o^Cy(3;O>c&C!sLx0WRpD9m=oI zaTsgS@{tvju#AX`GO2lc$tN5TEf$22M)?O{xXFyaJDHfjkiD<>HlxH>K$R9Pc|Ux< zJVaar#raaw)z5_A+4+;HQs6l=IT-uu)vnWL&zjI$vS<`eX;zlZs;VkNCZ(zh+?Oe3 zT3-40Vmxo%x^)VDCeiz zsnngZVj`Bf5ILWkNLgmw3I6KOCkxu!b@2Co`{H%-+giI+yS}-b09RGDyAwoBN6Ylx zpq!{ZNRy8nZSeGF+XuoEDi`9FJldT2i6bN2TEP6-S=kGTLbAq)f6-pU@( zm1LJmli@UNOlH4A4tR{i)z#IwD*VCE@6~I>g)HY6W`}&jA8z5vrfw$3VSNi>$0vM# zc8W^f{76j55};Aq2YtOoi->)6j5j!WT1pr~iaVF7pep>Azg?fX)3WE$R#sLTO`d$x z{*|bjdqLMtQJvJQpC4xZd-z{+%ELbkx{^vi^pyCX^?61x-rtYE?dZ5H<>OJfqcZp7 zA2F`Y>;G6$ZeJ*s{^ke+tJqr<$3SsEApP9A(Naoci6LS8P!o7r7rXp*NEP?5`?(J5 zDGJ!7;0KVqcWF^kQG{TYw4&lOfGE9@qwOa#Nw+Si@Cmrg+;~(3Ei4R#J1CWN-#$Zt zl&y)S58Ua!z$m68tC{*MvV;7yrWH&<3`TE^7WD4srjx_9pP*h6n`6$leVb6UqN^j0=VC`L8u z^?APo(RiS}sGgTF1D^4*wcqm_K;?h>^yyabsov_MhzUIY0*RcewCc88r`l)*&Xl61 z%a+B8*k+_R;*kp5)q0!CuQy%Ca%RuuuXqmfSKD^ z`H_i^+l+ngt5_`?pQXh`RyaJQXQHU)#x};P@I|?n^Yilu*AWc3T2tt*OoI2joYyt5_*N~{>6a1n>e)=$8KiZ^mqFZGHCifR;|ITB3Ss| z-FQ)n#W`y*&$p-+cPM;zD4emCVZsV;&Ug|`Qt;+r#Clt`RKur*UM&4SNw|;1&ySd> zx(CSRuWk{!S)LA^6&PL5LED!@wkFTTsTP&*{rmT8RM^{3Ri>q&d#j#cRmTXX|b`rIltX ziGm~d{@IC!pMVu9jyagmO(_Q6s9^eIkF0cbbVz5_$S_ZHYy&rlLf@qJm2*MeGnlXk z)K(}e<-VUxuI;7^1c?%eXJ!-AKTCYKpvqiq2cc5WbIz)@hSJe_vAvCklAr82*OX>L zi6s?e^4j^ZO@L0K^J;r9ST4*txGc_3ZT0qM5mm+%EU()J{GpuhIzRrIN#}*Qgh|~2 zh*+n8L({7}+V&G*2sav3YYhciOWFGpk%sXUv#D!w=#%R+pG4fbvplwl&2{=p)Z?+A zz8BQjY67*zXl5lV6pn07N=iB}CztM60qIOBR+-nomh`gO^_gNXa(Q|O+wM332 zCN3OS5V92GR1Zbfpow)1Leng3dp<5P-LdRs-%+Q@xX;&cTAQY$q~5pXWC3anq)OyA z0@9g^@NsjWhb(n4hgC|*j627)k)P0$f_BxzA8$K+(Y{ot{DpqqI-5RKs2X<0Zf$+Q zEi*Lgkg3R&2b(LthFdv$ub}I_m|o(36FZ(b*&{bTp#Z&X3e6x&ba@ZDh?3jfh=)R< zP2p5-0`uy@%K|#TR z%76a(C&}Iv_$S+b6lib9wZy&o2Zohv0I@P!~#CI2cru3@5I-WjKaC(^2o@XN8KCv!K#Y6-+~_0Fa^1-t8paf@ z?9|#$s04M(7{#8%ui)FWcdy{J!nKczf{s|wo>2`$2B2Nd{CeXZ7b0-u*F{ej(YhZa ziwjfvz|;W?W1)-NE`~u)o;=`xmyv-XJ+=~(k-FB4&#b_rR_C$k=qgThSnbh>>}mYi z#&s8djlPbhx`xzOzd-uU`(H`HNfo*A>+(b}mTz-w z;*l&^CubgS3nR+-58?TliE3%STmlO;wD|MzsMK>E(_&N-&Fxs!Tq+4K03o-XGLcCP#llhuFt&e3t31_oOS#5Xdtx4`4fv+#!5iWDPoD&I}3%Tdd*O2qcM97 zvewQCREq6oV`GD^FIg&{9pXGHRURtYPD|8Kwo?)pFFPvVjv`hj5w(q(xj#GxXD~n0 zaZ45w&1*)Y?acO zVnC&0IX}6*A@g#IL5RZ{RM+BBIXvrYv;{Ym;u~NZ+|Wg?U%yUpTI#hThM*!-KxS6g z>F=hsW?q~FTedJW$6^4CFn5$IW0m6rwH(P7FAo0|pe8z%K z=;9Y=;saQ1Td(Pez1vi^Y-mV)q>3#7WJqf zEHUw}^D~415gh+@B7|5PLWu*@jRv6sVjex6op177X1Rrgl!@Vy>`iWpFL&H0#G!jT zp*BApLach=ckPPWWhtx%r{rawKWE-FuX^fR4w)CPVAyRJk;62>Wry#~;QtP9AoWIX z)q$w>ZApY3#i@Q9ID|H`Zae=e^6YyPzN0r1%x^<$QA2~e1iuPcLXxnd$Dd+(8+iBZ zIYCwpwBNVrdZM|D9I}CibJHWrDk|ebQF1S<@SqOc^eF}nry)oA-C5O0@R=`t}Cx>v_3dDh)CuV@=Yvvq2T>7#ZsulCCMC zkI7V4>gi!wnKNqmD5mJk*2Zhpx9Z_;w%sbYYiiV#8jS_KXf)K8tDa@0=U4hXs5;F= zCC#)^9#}_^8WXlbQ&3AlP_UNvPSBuYr}${`UR$kBkZ|o>$7rCIKCDc?9=Dm^DE-i` zFP@L|P1|zRU?m9{c|Uw8kNMLgzX{}1m^6YdVFe+l1rBh@huEq*b17`G! z<;&l~sG(85b%nSsIGD9$UV`)xY{5~9DfK4{kZdmp{ZbH~9&`)?ryPF0FNUz&nsS{o z@}_^#54KW|RF52Kv;X+xi+4!oMR1v$NA_AaMq`qxzq{-%UHIjn(y-a->HU8NP72Er zEWeZY)+X7D!J4DQM&R`1!awVEM9Bt4qI5Sz3ldPHmZDD{^CYb`7VDabsDT0oUO>6M zsn>B=ms#9PNB6;ob^`5{9C*34Qa%{Tqx~Pl8bW+=+1(s1j)VFW7%a@VSlgR3&#n1WFEgN)cMIsw_X<~S%gjQ)HI?;5byZYm#lAjd|GB)<)rIWj0S zgL#pt>BL9fJyDQG(aw1bN%*C>lX8(aTYb7&GeMiw8N=#GPjc5-uMp1VA*)lVjz2eD z5W8Zd{`Sayunc44m=7O6T7UW=fJ>xq$P7LJPwLX8OEk(yir1Z^nR2iWiCshZJ1Moc zZg@=9Kx6!AHb@W62! zT`Z??KJxTbLNUXG)k3N5rER=i2up>SAmHGo!$}16;q&Kx(8D6mvO1iC-geMgQ;JN> zjT<*2JrM)m{r|W)*-*P$FK*ei=?xU3pjp}T=lN63aVm}VnRJZ4xUK*J17iqq=Gzyc zhCkm&%uFH#eeZOcY&5tx?$q^V%gGsl*LSPjdHk)rB{bC@czf4_&S>)2;X*X|y;5FE zxTv!^54&Rt_R-|WKhX32g5|Fay75Ouvl{UGk8uJz_p)Bwsl1ilK9x*4Z}G_&)Y9Ug zZ@eX{fVYUek*M+gpoBzQD?2U{*LcAabYUHvL`#?nV0k>08nSGOxP!=u;=9YoBO)TW zpwE?*lmOi-pEz*~Rq{=)(^OOKA|7weyDKLFsw=`~TG%O+ivM}kcfeSKZMn^n);dmu zf`TS>GPoCIOUrkyK^vmN!}r62g7;Vryk{!VX6A*Tbv!i`-MyHdbwgJ|ZmK%k(liWe_lOfMXp znj#CBm^*^zEvm~_uKkK>o)UKjqM9Q@6Dfk}0;v%6^u9d1WN)AO*YZJ724Rjk==^s6 z@PI9u+)GO^3}Unup=mD9HM0y?mX#Xse(J>{+6RqC1rE{L?fIDCsMJA#6UH`5C(bVO zha}l*aRQ{}?C;So-^9eB*2S5bnI@Mb=%xjTBHRqpOJi*6cz=5xy~g6^4I9EN^5>O- zIUhH6!T~1_eP`3QEF4zuEwsr3Fo+I*@CMRyFegBHVu0tebmIm{@Z$ih3A7!cUQK0b zoGzYK(vB8&b0kxf|bJl9Sm@&dj-03Y-)@DR6Z5Yj!5(4j zZdGwkF|ii*G<@XeW*M&KEdx*sk{q1}8j{YRKQ9lx5J4d0bL-%l)WoT!9EOGVs7EnY zS@!h9&4+ZIQN)5DiehAlV`SW(ms@L1lvu2HW84%>UE432gk6JLkyeQba3Mv%JlS!< zBr&)9*>zmL)g~J57Q$IPLPNM)FiZc(ooi9R9HA*dR(^P((glBFmm)qU_%=~u?%umc zGC~Ur3;)`2T|+~mz1djc?ECiZ2Q(sao~L`y9)?F5WP*EnQ)^I;TfEHbPESce49d>G zBMIOl0wd6nlQaysEX{S^@ne5>=W`Y9E7e!<*;SzQ!|T1pM(@`eylv4#wgL9DrQLS? zllFsx74}ciWBYqT-IPHETeKdkV-b>&3^F=23d%x($sv6I;8csXcck3Q7~pXe*oNvU z`ktjG+k{MCkml-Z@IL2~`_dJMb&JzA)a@xT1ZH7CJrxcBL zm&fAlja`DfXCbsafAZuNvF|Z@WUdfXT#FisH4QF$0n!H1hcV;Au`CgGxJb-76cTFb zls%N)Z-I9LCh1SK)H z=+cA49Fo&5+FhqQoqvxSA-;gHf|ZGu*Zl_%zQMS7_VnrXjCcT|zQ&X&jee^LbO=Mt z>wqS#hA3d)toX(=;G@5LHPD@Ss)Mm$xERG7)rEyt@BB;7$yBrg=R5 zLC&81*Tu(){0owu z?u`nq$#+})OF2or2jsUAk6iG9YZdsm)O5*cUu%!=%{KR z$_%2{JXrCUbz+FnT=kc!$fY_>nScKDsbxMBh6RO1Qba@@x)>2=$iQJhz$@gC$s}P@ ztm{`f4gue2(F=297vRP4P$+WX&tF0%^HCPiFk+DTAY=ti;<{Y`U56klTA3&F5yGzh zJ?q1zd^N}R)U)8;r91y6ck2;iB4VfZ7gcIw{Lh%VT&j zRqYCX#&I282-Z5x_AS$p*&c)n#Og5fEUEFh_D4fsYzPi6~P_7?RFoAQb99_ z%fTz?;EN0|^p(;lNX(X(wlA?-yHrJp*m_uC5T7+8xx{8>PoD9m zZtYu;cJtWnmKjiVOum1(8_)#1)t6_fik#K>uS*ky&GnI@d;AV)q|1=Sd!*g@%??51 zqiGUn$t{9h5~A1G2||%MTULt+F+q-rHLU%+ObLtwdJH>mtoXYGvGFij`qJvPt0Cvm zYGyRAz*0vrlSMQVgp66j6`${`PYjJ7gqU1|nPM{CP%(^35#S2lI0FK)ucR81b#c*-ShVA~DQ0ap89bQ_X3gp4;|7?lGz$H&2$oA9@+^K=Vo?T( zZwDiVH^dhi*r*W4^CaT516GRuo@~VAxd|6bzzAQ0S-(K1qRC#17hgkZit{vis z57#8jJl+a%dvs|h1RwL*Rl{fOwBul1z1T?)-yQCAToId_CJ@7y8wY@~;E&ay%tj+l z;A`@41T*EDGCju1i8kQ05*rBK+LCaiJh%sp+ld7G3gJO>Y6Ow5~! z2|+fG9Pwv13X~RBULD3zHUZ2jo2-T{)%4e{T@!?~`l#rLX~U~6Jj$d}Q{w55Rh;Uu zItJYW=)>Up$OFumw^&dl>kQR49NOG|kc^)>_{9(?80NPheawO+?1WlkPK^D=V>sk+ ziDbgMm6l~SKsPVXQ?>Z^`Ywqizk7Eur5-1fX{FlLL+N}&lHX+B-w?xN+r%OBbrqZQ zW!1s%omiX34URvbo+Ysh0(`)A1ZK}@U>%6q(c(y=pk>T(F&*`l$mJY^@UN;+vaeqM z;uIq0pa}LvDkafoAZeP7QU_n!wS7ZNRD5-8$;ZCR1SrkMh`8&!@hw&}TjYenlF6QL z>~DEkzr9#$wI+iqASKaws zNF*fXkE=P=?xq81dm(ZXRfc#}98OGVAHS+ENKQe*g5HLE%(Fc=e-6DOa2Gm~51(_FzdE%R8n@ z%SI5{&jVr;Fu#p4l{v0R`w$%&-LenS%EQ82w`?ImWSg>ut!BP!>wxIZbcw{3yLx`3 zPs+Higm&@^%Qj=y^Udy)_ugVjaA5gbRNB5wZ$fml@p&I`&KvOkz&pJ2^O{XQ?9wE~ z60lQ)i1>&_1;wrvVv0Psp_4 za#hIR^t%bL4<)8*mP{syQEDjv zg>oGZ4@cLy83}XDsz5!uMKnT|9o5=Mz7qKk8uH~y*-u+})!$;f#~1Wp2rhcIcMDLo z6^MjQc0q)gExQ@FK660VJTU<0X`EYT-|+4VD3VrW&ugeLsYcZtNafT;r|J7=c{EK- zS!H3e+B%58D@U0UpJB1hY(pO}qgY-^;N|IdrQi#N-ou8#uMLaTZ&x^+t2Y>_AH9~3&PH{eQ98TO_vwcCB zu6X-uOmZVtk5 zxx}^mE&UZ%Jr$SDe~$qtdCZQDj1YhRK;HXn`>Yk123yZ%Yi3y)LyN=b{iEkWGAD}* zY+#G}rekjE$6mNV?cBt~6a~@55Pb=~|HbDqIU>MaWAQisG zCz>naous&B(?7*Jlw6oT2^Y24_GJZ9WRlQ8fY4=M9DJ`gLDvW*xW>-^^0zzxY$~H< zj#(f-oz}eA3Qg-pTm=&v;&rZ(xbUbbY8TBfYdbxTV`(Tg1#gn;;K75{W5)@6LYWt% zy?3*&G)9PvTf^>{H=Cq+l`ZZ@-$6@LQ`1j6MMD6s#tcpXfdaDg%U7->WqoGIZP7$w zKQZgO%L3kD4}lt)k@gt}VEpbq^Eh+%I*tgzg%^Izo#7S7_E;80hvzSFF>xs)Rw)_4YoxD9P$1w%$nUo=r{axv%ioOIGDQF!m)4PDWLL_Q8A~; z?Cqah3iANCET;oltSxl0Ks2#A2lyJ&N#H=2UMwV;7pcP88(grkYEz9AkbII*n8m-6 zWQxbk;fGlTY1r4`;XeX&nhIe)D4G|I9gFG9sBk6>_v1k7wuYMXEH_NGzKvo7iacyb zmYE|p*snsu9PO^N@u(lq^)gX9LQxsUCz~yr#kbxJeo&zI2C&;V9sevW_Pnr#`oK%9 zz#I>Wqak&C7AjmCiio%UhWm?&C`9^9dn`GY`rAcoWF&M~yfmrG!&6P+NmjFNcbN)E z1=3Zh6rT#7o1Y)->xavxe_IKHqFJE!%a_zu0IB>&0DB1^=Sgs#!@aa+8%;Us>S^C} z!Zd&%9f#kq8l+XWwCljWF;%~@kBZsdbwK6zz~8XS%fa$TF3!&5SV1vprDWF&Wb8e$ z`JA(pgZ(~p*w$%_xVy=qPf)z&RM@*X@k%@nFvqR85(Mqsi{S_j-$69Tj{8TK+ZcWVuAUUO6-y z*+|a5A<;Ew1eosa)Y_r7l{V*awF3;EpStcnCV-*sijyXJ^OG7W?wq2rtW9qaHsg3C zOSlZ`Kf&0L-w&I&WSV-!yAkzf<=SnpAT$}lydn;M$#@A8oZ@KKa?G1?i9?CtRaWPs4UbvcDOKx4A5Wi%U4fW8;4k73H61+!lv zRKlg~UOd*Vu49oUR35e!ov1hU$ zf(aVCaQ->1a6lwNb^#>JMy>a9aGW6q`nxM1730+f3BZDC{P4kpOJE?it_EcL)TV1$uGIN zo!pSq)F`9^hABHtI$t4`+^?CPXkUR~Ur@QPPEJD+lZjkE1C>s$KdNv7Jdc z9A-f+Y$aur6Mjg$`E=cjsCzN1J-$`ccHm%^$-p|;XVKVrXNd3xgXF4uu#ViZ3Q>yN z?ZOsj&BlK;vFY@X5f5?-Ctb46Lq?l`hS=ufTlRGRE4Cpf0=pkgbqm<*#U_~mD>zH{ zfI*2rADesWX&~s@WIgFeMOn+}?SN~FH9Pn-p4^Ks)9No~Nx)b(c0W&pGBttTZwRcf zKlLi>4Jp%@cyaf_LH39Ni*^+T7ZGMTZiXbhcL-aB!zMIV@D%1wi1X$%JD;hGN=znsJpF|iHsmVT(At}*sr8JhQuL08+jJzV%rpP5`AqU?U7^y z3drcxR6fT?{;mYHKr5|uqvfDq?!0jY1mFqSaE`eVY?R>*3VZi)u~py(31SSNW!S* z76vT3YO=>x{&TG2Y)A4((z=tmhQSnRU!QHWW-P8p!d+uR4*~A8WWNAz!Zw58sKlJn zhh+Oh{AI;tT`8i?5jzxw<4^2lZunzB;Zm9*A`xeK>As&Qg#wYi7OgX0Uy-@>82SbJ zP#+GJ%Iy4w3;y2T-dZkbW1oLIG>F>iTv0>*qjhmBiWcj_Jl(L!a*F6-K;(UvAWX%y zf;0pId_8>8C+aBcd zU3_Xy_Af$13TTQ^jyEpe`~k4$&&J|rPzOFq$@~L}>##WnW*$GUGT9}L1*hTit1TG% zNl>adW*(*0n_Co{Y&LJ&WIfeM(t;m$Esjc9Z{3|1j)`1M8Uw-Tr%Rpa2*Gd zijtKi>Fh<8`m zSm5^x2qD@ElYphIbqy@QU9Gp*@_`asLR_jnZv zUkt2Yzy1}N_%G;kWi=A6bM~?U{GTK31f~X?4QzY+@lQw7lrN~j=Zn|Q6{!||iBN>4 ziR>ge+eJ;yVDfT_$zXLjkUL%F_8mRiu+P$=Ux2gN6kS^A>DOlP~x=Muw1;;){-QSx6X+m#}rw_rbJGN#byCMihLdFb<0J4qP z&(GG*dK&MS_=T^@W229LWym0{>ZYbgfnF4e3jsUoQ8e;h9D~sE z6yZ?@YsYeyLT`XGPNVUgT9MuA2v`t3AA@}Zn~L$l0h(w5+0}_j(W<5IHG;T7;6N|2 zT8Kk!9?Qj9D!Zh=odlnd>GJiwF^g9@fEULA_bDf~BSsAQ&#a2PD8Ra&i2}!n5wT)7IfqSR@}VXK&ykQV%MU? zF!Rdw+m_orW7#a*b#1@#+kI-05k^1Et7v&Y@}6`nzcxw;2*~Q$n6R5mJ@aP>M_1ly z%FSJ`53Jd{oMCQZdW~d}Ktb6-gNn5+iTyuf9Q3<|EfnurhmIZasT)SV?lMfaGmJTQ<~o--ov$~@xttDLD$hixBxl+V?qFaM39p+Z8_cE)BIKhFf_ zxRSt!Tgb=Jo{}6Vj6GdIiofi;SA{Ad+bKU$sE8Dplvj|S-#r|#Z+-!q>yA{`nBdXT z(RZjJ(WS?Lx_Oj3Fj8ANE`&VB3|5a9=jB}o+H@KhOX_V+yap3`$7xN?okC_!cfjHe z2P_I7Z+{Dm=xNLmKI~P!mEO{#DP?>}4}kOqaIE2cJAzw@yq8l2QCbdY=WP4^`*(r0 zTT$1#dh~>PlP#C_C=?V#tU7Y!2+0t3e%OSHu}#qUneu!NM3QX?Pp;nLbBNqV-^Tkg zfq5!FO0T|^@L0S37Mkq)i^_EOA3r_?IIKJ`^Wxy~{w$qHnP|ly=nD~fXM(7UeCK6l zx1_e_yXo|}o|A-AdmL`S4fUVWI+clzM(6M@6KqtZ?swVr;DNjJ1?)y{07qyG(zT_v zY{#&NL{m*4MctRdU zaNRJv_TkPydKI05oLA$5ov@0JH$w?}>g&5qR8;gfTOtnpn%D5z^XKg6^SinR2R#8F zUL#rwzlVaYbRGB`#5)7Zg;oUa-@ImedU|ua;9mYCYc;#RV#5}qyXyoDDn0>Rr@87F z8tx}@{`CZZMZE3fJ}EPXMUNgm`UoBEv2D&C!tk1P&T#m?uc%P5S)W8k7XRMh3yp!f(BYbt_a?{#ezJXuQ*4Eb2#zu*LV_jX{ zyOflZ;JM#_{aTGtIC}KxYS#-mQ$u6p`)}Vipg`U8_xD$J`Wmq3?g@8{_lM?YW^HZl z;o;$%RH|r8YwP#EK6Tn4_x*5v5raAX7%;{AUS5Tms1L!+e}Gl7=cQvC$TnPuYvJVutLf=|v)2~u@~>qTYj|6fnw)&K!_iBI z<0Dx32m<|hxG90%(J=YBk3|uxbp%)dU{@_!{eDeEe$tKNA%Owm9 z42I-gC^e_HtXk&3L{(k=HF^SeWC|pwq&)IpUs+LcFDi-?*_dT2Dk@S5ftxlwH4hFB zJ_xVuF^lL{Mpm(7Cr&JdL%NKIhX>&_W_UguTDHd^q<8Obrlbg9^iONmme&+_ zS-p?U<0HCC9Zz#%Vd0v|MCWgy=(mw6?^iTX14rK7v~&a6*q&B$sFx2|mDlupqh$>= z7}!qBF)94wgkp|v%e-H9Cvr=kjFzo!dJX&fr+2W|>fn_RcldQO@Jf{%eKr380}3`I z;Y~2aCfgR5-!qaD|6{)yV-?Et6(ph0#CvdacwOI%ws;%+SBma$*t^_1gZNh?6}nLD zMvz0^mULoI2Z$)UgdIW`Z|Y8LVOqxYG~i%_5^tJbMd|uYPZv zevPvnz5e4)Vb9|%Ee0jiwVJ2&Kh)NyRjs@U=jj?q+8Ocg%!OcC*#{wfvh`QugfGi1i)&4sTS7F5Sg~ zF!brDdYG%P@EQ(wiJ_hz5B%Yu^s~JPp5Sd?hZknCHRyoE%&)y`SwuF6x=aShP0)Sq z7grSa+%B*Z5a*1uvl#eOM}L2*OJ?ya%!0LVp?F{1zxlIwe0+Sl6>ZCgr<9!B+zkMU z*WD5zx?Mq@PI#_ZNNnqhmQ|5`Z7NZOaiCc4U&`Gi54SmYif-QpZECq zxYfirD9s2PZbGo`1H_%LNczZNcSV$^M{}ytZ8X-)pfuA!Xy3cK?t+lUx0&1@3d_-U z=-MLXec@DO>Famz-d*h5bC^qAPDDUJp-*1sjH&4X>|IftYpAN)2!qHYEiEnFrgtwV z=PI&uL0to^o~QPC8MK%=a1>)Ri0-%Eym_N5c&W<@;!iM=((&=Q&9_LA=n5(cK^9Tg zxOtqz_X^?Kzq7RWv9m|C(9elEj4lTX=;{gEU^uhWus(qWz>-}}w#k0{*9Q24uj=Y@ z9IRnyok_@FpK~E?D@|J zH~zDX>CcCrp2L{_`8oBW0F?|s`!YmS2Ru6)jy=tUF}sfYx7G z$p{ap5IS)L#pBNxWQy_2Q}j+u=RnhjTU@Isbfg*4?Lt`UKX-Izv)-N8WtU^h_E-Yc zFmi~DKK=9M$8qo8J#jBx50&RO$m3pi_7&Je;Gezo&;9T(!7oL~h<-;MKskvn<$7L1 z3A{vVFF6ISegiWzJsN7X^xpw^AZ9;I2TpbtFd1!o7`BPDmVC6#3PLb`3!)dQ}aLz1-Yu$oBT^eSa@0 z8QRL;qelrO&AidO5ieQ`r$ozexZz9SjRyf(v8TNgw#8?xrwGb}zqh%ci|ZeVj~kH7 zJ%k-UDXTRO`ip9wh8y(=VEjXSI~&l!9jv|@_CFW(wAjkp`diq^Rhl{W8<3|K!p($J zq)ha$d&Cx_>#Vi2vm;mlkVM}9KTYu@9c)Oj|vM*Y5B>Oaa%4q2DLBg%Ey&- z1%OuTckXDS-Z@c#HT}Cb^ z$3Xz<@E#k8EBH~A^XJn!gABz}AD>%LB(C9)`c(O>#+4MhOzR$oHrH9RIVgDV04AZ_ z9K_Bi60Pp5`ZJd2&ISYosOH!oQclo(kdq^ZcQRbXe0U4fq9&rZJZ>2i9mv`?;7cy; z=x{({Qge|TTF%VO99>F!`JUJOKfAjj@oiB}H{H%JAOIUGXN&>BV#%sitD;MJm{J%u z>F~B9oNqN$*=2a$)N(p|d;3>tw}$O`;&_7sfEkZXz@OV0moYb|ptj#d9sG`gEQEfK z_pRlQZ_Jw;l}5^<;KPR#ef*{ICzdDdK@t26A?qOajqT&)WXfo6YTC1Z|7swtTNxQg zT3T8bOm ztFin+s;Bqgd0)2TzhyoC@7X2(7gu$a-DXMQ6RZ-{l1{7?MBCnDc3BR!iDQG%v1rBf zTCAjAT)NBdU-}(02VEmGE9-rE`8qsJ{%pG;22vbLH@!TKN)c9b`u=VTg@I&C@#Ygx zK(kl3T!a2hV*L_O5G2pI@f|NnF@#yl`|o?WhA9)Z@DPB}PBHs;ndNnLTfujM{I>u? zku#zYWm2gpDgM&xe19(GbfhBp(`U~V#lBz<)chJ7PB<~M|t%SjkJprN^HvOsl{3jb58&&h1h2i{p z4Gfr)UV;m=w6gNaKAan1)&Wwm6RJ9H{0?f(WfV>#K-cJz%inNG81@zmRxYV-7`G&( z-cnvR>-o)Enzd$cA^l2Tej{lHC?SVT|9nSjJ@z;pz42>3T%4y*p4^1Nb`8#mynh5! z3iaazq|FYfW5--wCE!Nig!^=i!&gdc>!0s#SJl>LB{zU!{Ls|218;W`Ma>GQNOS6M z$prDcjic>FZAQ^3fNuVuHg!q}T4|g{1_L-j5%_UOR~NgDl)6Ar!Nl~)HAFLhAZNLH z?b=V6AeYU|c>k^E`;zD8oR_a!)rqkrB=GvoxDlxOIW0fsva1X#$B!>TM#vKa-usUq zmqA0)ZympfcQY}xJn-V4Vj4Q-iFcf3OKO=ugQZ+#$_Y* zXR7L+f;@pgEjKkY8^O+x?-+|`+56&OICscLE3Rf_Tt)YIr|?GfrT;W-pJN~=2NCh2 zD}Z4&Jl?AiEzRuXGtMhtQ?mQwQJLrb-UV;yGb;^BKf2L{i@`k1+!szkNh}lUgExBkJ z5lg(7YL)%ge3WJ&+8>Kv+1DcW6RH5kaS@Ae@L7A7Y%KC>hHhm!i5V! zbl)++L^Y^kH_}bE%Y1lmlkKms?pOZ8fEv7humgFvRWi=1VI?aQfV!5^;p+Kpxc-j# zEkGMOI%Msi07C+?I+Eq{3e-v7|HPk9aJ=2mvsrJIg6^n$X>+s2e_$U0vMVX#o9DxX zMMb5x*8G{B0k2-iLA@im6)~d!3TfDci z!MrZ*zZ1UyUsUe@Z>&REp6S?3bFv;i%CXj5!cvKSZAe<3yW7G*=`2u-gbq#Ew zUcYhU9yQr)IF277uhljY9*tMiNGoqrqCoq6f!AcJYoM5ZeRGzv#JEZYfC!LWYc7+p zbZi<97c}DG=iewfKklmzwpiUUG{gt?)s*J~Z=fNpP6#0^D77ldfse5%e--4;6Ohra zYaBa9hrM|F;-%S;b9{je7xunBYKHCXjeym$s_e&l;G7Lv)=}!s2+ciMoF8)YsSS@s z=%52SIG?NOl`HNrke8sK_@`3%*RG^&+RV)S=*5c{KZ+oDZ4PU87BCLwa!N}a#_bWE&4c@ZqAy;*{Y+A}+$);~B{7v@K0HB1Q~M)z|U9a3Y; zE@Kdv@h#m(MOM?D;f*|XrnzeaTB4a~Q08_2{rkrnG(&@tNY6FI9Zq!9`}XaF2`@=_ z^ktqE4PRvcO-ISe=MzUOD;L73t*VCLppdlZRxy(Vchm&Orflwwns}~}k&cUt60uW& zsuYhz-=S2XYx2I295`@btV%>lW9^FeCQh7S#||fk7?@iRh{)q-*wd8@Zs+|^2TJSc z?mIG`4)$8{f@5k4>tg1QffJTX3%i}NX^Efb&YhbNW4#xi8BLp+pddkflIK0ZQ!7-u z9jP)1j&cmMcfAzZcH7$OM`gYEs#fuly%ADfKCqTM;=J~SQWmshgt1PM3sb_{xpZ>hY8(Got@#D|VdgeBCEyvk` z`q*dKuxc=w|8Rc=%GU8%Q9XS8IMDRX7amlnYx_%ttU=mF`^L?iy`)cT^rdjR{JCUo zOtYJ_Qdn1H55>nOc{F?dL4S0XA#l);>4VAEzQMu41+L0B!PVDdVqwyD=hiL9nhVFK zM*kl!fVvM}fHXq0Lj|YMIrOfFx|_6(*?SOUuI*qzt;*^^KQ9z4HT)53)gfgUH@F%_ zYIK2$q3Ly;ZpZV_uU6fL?Of`{uV4G6Y(L`(?KuPj=v+6;*gVF$0bCzDC=|=phAcF| zx3id`FgQM{>#L=urP1CLbNygVj(3jIrC}BQph=sSLYRT?i)(vc0@T-ibfpRY*Sk%F zVe6X8GTqm1H}((n@fb4OD#Ni0mq*3mx#P6{+3WWoIIxqdsBhoCp_kGaa0bauxIT14 zShztP!;3yWA)J5RB3dDOKRdJ(w40X`zM9lE|8!aG7jK##YtW#kG+7xbQe&QP2N_kmrsL9fhYOSM3+&@<8rpl(H8`8LCbb7HpC6vv*yfu zqLCVnUhiv%ZB*tECd-noCWrwAH4k0Pb&9>7a&lHvCVt+`MZO9XFdc`%-#>dE(6_6p zD4_H1G*-U8)GYOKeV_9uS~-0k+Rc`g3pGvDIBH&z_viPP;ALYqH2MOA_=RxfgXqK~_YU?^6LSpL=ik#JDUJ3SrjG5pQL@xm_2jX}O zsBPze;mVciA|BnH_Cp$r04i3$nib=L8K?t`kw6S@YaP#QXxGnUP%%7~b&2U3urukP zl$6w1wN-d+MKi-Vd;YwiX<{Lwt6~3+DLhwZ9%s;xnx~?3zwlcN4#jC6AKGW-!Q;or zS(@~1M|9Xtz2;Iihw^bPJSnly;Ov5YfPUBJe4!USbu8BOCMB=%W&84@Fj8uy3`c{2 zpjVcvH+FEL}ztgkI#pS#G{9?G^*10XjCCdw|k6SP1!fc3~ z?vy;$n;3Tb!THYcS)*l}(fKjB+Y>Nr--I*jP1T1Li0)|=s%}qh|cO6p>8azOr&l8 zs0t;iXEvB(+`=I+D&wAM((fsUuN*b}xSG4UY2-(p;K5U^8=Rg^UjKFnp`-KbV?SY8 z5G7X%?}V}zz@SqgmfEdhv~bwT&b)MFQ=5Nm?8>>R@lj*kaxPtR-5{+lJ!j4w=*6=d z`Z;NSFzFAzU$upho8aNC6Tf?BQg5n?a?eeM4^AH7tMu!XN_jnO`gGf7{Suw{Xrh+S zdGRMq=b6EmU5o--qQA5_J32TdWWMj?@e?LY=)>qY{Gzma`^fVv#y1bDWp17OIQSM&%(6Ep_kx%cuk#dNH1SKoQRG*&(A z$r!CqCgJ^g-InlA^+LLN6bG}{w2-YTj)4; zA7$E=YZZo7z}W27zyIiOF(q^EJ$?LncN1{U%*7Cdn?KI)V=UX}+_`hzyrhSp@dOy| zU6HxIF)(TJ$|Sy~PuMPj(Q(M?mkjw8CE8-Bx_VQWGa43JmP&`nOPrVY)mesTwVxoA zXo4gd4GYDyJ}!I#L^F=%2vyY{fqVowqcl z8d%W)Ykri{(=i(?Z4*|8bGL*WX!gA%RpYU{SIVC@eqGT2+M3LhW=gtGJ&N`lu6S_9 zT#Bl6Hsf}QU-5Bq%V@^6Tk9FfQ;A7wZPoN6L%u`?>~-Z2*yl*OY>f}Yoc+BwDIJAZ zBbO{#Oj6a@T>aAwiR;a;o?!-xi;^veIj;dz$Z-j^1}KeHDHy7;q;al+!NFLW;bX?T zo2$cd^9lZr3;gfZx}560Bh%y$4Lfpg+bn^|@T~&JER1))9DOp7`!H_L9tj;f+sM{CMs2WtzVR@5{1}h)Kz5pT~!3-!ft9Lub5`x4(=@ zq5PhYhjmoKPjQL*t2@s1eeNNcla!LOnSr^9Kq48@XUr+rO(~Gb*pbC+PGXB@B6q$en>Y>41J}r91r|DuRpMgC~*Bj zi%#7=E)0^h*TtsmtL^S~*XKm#rp%Ia+SOkmObad12nv1JYsEN~>_2g0ABDc7qhmf3 zF*GeR_`)x0YkjCnMU`^!=Ogr%J;hk8={wwWH5ZtwTd*y7WF zaf;;koHt*NfAg58PvxQxD@HXtlxwHdKVUFj5bT4OWYXF6ZS-TdQ*Moi^k&)K+0^s-tUY7ap%H^Yv(s1J)Kjiq0{r$6*NCd*`K9uZf?fg zB=W#@Mkp&M7rvRGuHI>)tE&>J8i#XbD4le~5L z)?4(Dm*yFUblfrPWIlP+1kcuviwx>aOx=;m+wa|^c2`&oobc0$PxyS|g}Et%6QA>+ zKSspW|BBo<+pIT0ORnEycI=p4dBvkQB);h_yGzKfu1v9 zqOA5kefo5zQ@t?ty!Y@oIXH%YCm;1vOVNzspxoZXRh^Po){QboJ$75R2s_Om*Zjbzmasm`ghXM3`ZU_qi8E?zT0T&tHZ=ld4f zav6&L`~i3p^f1rQue!jFYifKk8~xNvVmLi+m5XH^1*+nJww0+G<7CC)R08IAuMOr# zN=E|&rHItN#O-CYx!S4;L&HlceAffG7293gRb;kc!47iKt5>hUglicMcsACw|KH4j zW|)6!IOjgo17YF4#2)2f^2Q}J9HH5t>lrRd#+h3E{P7?(bM;f}H*OLdiEqEG)1NGD zqZhMabmocu-{I{nLu(OJd2;2q==jY`E|zH+l|P(4iCEW!lvF-esOqSJ!@pnTx<*#1 zPF1=B+NVFQu#T7Lv-wcG0$3veLIV6wePulXlAc>}_i$SzwD%f{9(8MDCoE@zmTsA; zKeb0iPthTgHET&bf~`Z7Vt6d-)Pm6{IsOp@FF`!f?^X&SHss`j(E+O2H92k*OMbMP zJ^OBOeIqhE)2Qhi-}i@KsNlTG-MviPe{a*8?NAdMTU#f3UNM^#ap&|x#pn;_E7j8O ztc+Y^{w&VQ)+TJh$p!A7-OcBA^_}&u_x^|otB?6#Yh4m~p+%eCflTg?iVC6Jo&Qp& zRSfM)OOrga9hNU2Ie3ca4y+)ud~MMQ8P%qiL#ecQ?JD{*dfb!nHS`h_m1$zbyr$2N zF!ICrrBkO)b_mDUt5z?Jby@fKlZ0y(XKq}GH ze)MGUEmB%LN%!bc%&jdoA9dh}xc|U@1$kC@s`(nkRWyWKEMFd9<8qXLX1b*KcONn> z2onZ_C4Tb+6f2Ytv1t%x+%kdl z*VlIe=Ladsud3?Df2U&cd^>JtIUg&QPC$rxav$;iuEE#J$d&p}Cf&c!f8!29ZEEX* zRGUR#u9_LUZ`e>K&Qkq&G|o&(jH};m*wD)V$z+DL?mj!b^v<2b7dstaDX)GejYn2X z5Rz<|e+cADF7pcdn!c)wmBAJuM!WAr+qs-z`d)J8|b+pK)(3!A!TE_7^VG7 z6GNNl@JdNv3Gy0)$Bas+zK2Td8k?GS?%DIt|HgY_NRyqNUsNP<=gyt87cPWfG)jRR z+qK2=+BxHsTUy>H-q(Ujy8ivc^5m-u2UK{yC+Us4tiBfo-y$tPZsqFTT2k|RMvV3T z=rhHuxib;X+%*XjrZCU#xF~+mY@-gUdwB_LY}>&h3G^E1Ch#M=O`jH$%WU`=u!cnE zSzSHB2=!wR10Dme%^ytn1_^zv_(W94Y?iu3H7syWr{F3@9oK8-LvUkE{mV z1YgbU#~&;-qEQH(&C2oxa%|deys~@bFoD0hr|e{h zmvHrr<@BeqVX(nEr~X|1C__T%Oy+r{QRkjT!8r2=R#?mTYmXi7VL7!y_qFAOuv^cb z4Te-#m~#|5;`#IEX**S>by3!ywBFr)blw1DaT~-5y?5{2w}794N$E0u`}VKDWQvE( z2x&9|WzQg|)NW3ue7cGxxysMtG_~QY8wVbuuq{u`gkKo$>RT1?77b<0^De6TixwTe zv$GVE)U0u{*K{VQ9)VUL{fj!d(+M}vUt4QBh_Q=6-Y%CK_3PcyLa`EtPQjE^I@Xue zz}iob^X$XzOwXpJscvryjiG+xgk)Dby)E2t__GK6C3+f7%kB2CCJ#8yS51G@_6s-r z{Bs|Aauvr`hJ7U{?Dq8~Y3VlFI6FHx{Q=N)AQV$~`B%G&!5ro!u%qve{@Pn^7FDr$ z;8}qK87Ci%J?!Jt!3cq0j4S@umnn%YAF?kronO(T$Hege)L4$55b}@TlcI5YE_+wP~{2MRhAYv9ctW+@oR*(_U*G=P>!ZK z{E9ivk->@4(e0@4N&UJIGZf`!&aAMjQ9nr6sW5Wn@M+TqV-R>4}}1&o%s1oy~O^s9|n^TELY(Hkm80LjrN-Mg63$) z(A32Zl9|Sh-xeYxt<7oU^oM4D@XgJgn)m(nfb}1$%^PjZ%=+S(D%9ltz0cLlzEQJ+ zctWfhncEtA5)JLc(Zy=7x+X^=v6T=i=Y-zm+9$G-vuZn0K@C8V!F!5y$-}t zRn2~_GL9W)Qzt$9$4nBmBp5+ZM@+Xa6_vz)^-Y5J(m4=TAZ6+0SnEl)$Mw9#Dac_`o{QTJm+V!(f+Z$_k+VvMAuCHDD5YDSyGz)py z(cqujt!YU#SY1bEw-SEe!=xX8#f53>eROSknm2CUVw`Yh-nD{HPcMyf`Q>QMv&WZ2 z0%H(miy{0T$)q{5h(E;;7ci*Pklupj&uW@Qzp^i0w5O9gxOB6*Qds|qtB)1@0BV`& z85K$1cOaU%xQQX|s)}=J-r(YjsYg~AF%quKK6CiIz=8{^o>Ceft(-Q!tzx}>^h%R} z4Cjld-2eRc&TW9fUnC|{;0eKZd-NaQj;~&ICXF=OFYSKYa7Gdw>Vcp``7D&{)wgf# zrrSFG^qImVze!p9OrhOYOf{XJ6|Hr5&!==20VnvV0!7aQfd%3@zH`&F&2vsyg_h2~ zcfPluLRHbmgOVM?yX^nG=g!@`X@zBR-MIY3D5;MhKgLv!%A!C_G>q;#_+lLQlb9x(iWw5AdV3SonT!Vr_ z3{v%}CtnZO&I*}za>t%MdkTKszI983xy^9;41sN4|8A{IU+;%~j!x2V2tOjFO0#0< zWvEyy6x3g>1D@*gssTrYnLwi9&=KlU&%gDXTmCv4F8ECP{jym7KKcz!U%p%ej~LHp zf#4zQ71^>nfgctM~&>WFE^A;N(7nyBQaLvJn93~!Px{xr zW=G9tG790N!91;R->wouAMxTvd%;zK9rN_*ATV7zWVN=-3l7S34DZf|_oEkF>=q?O z7PPdoDxf5!f-AE9Px;{P?!JRCC+fHVO5uXn(ecqF7bf=kq>Zz9XBKRYdBff7&ezz5 zAD)S;vc0QoAnvu@fcK)TFJ)zEDvukt%*jcDZAT7HKWzbbn0`37Kp(n4YscFYd zoQr`hKmv`$nZAe0jmv3JS2yS8U<~wCzPH6-zbAK-{gaZC>>@VM3+x*FlDpAe!T{ONZdwQJiWqV zZo77umhLNUzV-Lkn720bM;_|8rmOAxrOVc=vE6#dqHp}M;T z)>#Jc)OD&DbJ23j!|h>QI0G06V;;)TAo{la(o)0E`OW|h1Ver6j&gTEK7+RzrB9U6 zlHp4iv~>52HBIa^He%Wu5Sso|y)qYhhn=^sCLpe_o_&K7RTK=okAOWUIm8+KU?c~8 z97P{+b5xXQq>fDAK8-3%aY=vw8=){RYT+`@MwcJ>SkO4}f~_Rz+ITOvw2X#3@BH}u zIEocN5JU%j=3-J8{p;CiCy>g_-+TLK&mF;BQpCqwRk{8NwZdrR`Xy6cF|7vW3)}qQ z4G%q^BX78M8iO9(;tNDAv%!tP_zn7p;8p?*454`G;8*7cI+^D7RHcWK$i9pDuya36h;s5MHOR*)1`bqyxBn+qSit>>2h#h0 zH0{)@mwD$=^H1mqKvf)(d`k29`y0PCzA*i?)Eg68R@9!ieoQ;jX|^_CshHh-StKpJ zSf`@)=ZcsNHFtO)jjPgqpi^$e?9b;7LrB3?V0x(62%YAo@R>^-w^0zZ@B&^&TJm|& z+VG$UD2tDciiPWZef^o>eCw^ph57s6sQmi_;kxWa@)2B`Qd+l8**0$=C?_ld`A^%% z*<|-*`z7Pv?CpQ1391A^wCmCfb_E^ak#^&m``pfJTD2xtJo3M_lL1Ui^@+!>={m-U z9JQ%kA(3`MYP*#)z#gZftY6*ylKmqCZMQb*Mx-rFG-K8FXfe1Rh$6i30Xh<6w`D;f zM1p3+CeW5o&qD}LeK^|k;Rkzk>0+|DUUmHVp0GO#q3(A|dkXPFM0_fZ7$~96>^HG{ z^tvP-09x;t5%01yWU}Cmmn#sB$anQ+V?G!xL#0WvN~YrhQ9iW?fX=YKl#((IG0SOxd5GHcnToyiV90TezTV);kL#tSQb280 zE*+ZzNo46RcSG~L{$|&%T}=#m!n512VK-mGmPYVq#C4^e&{4~RoVeJoUcK5hTvw-G z5TBpMH1_^$1aC-!M4jNIQvCkiy8vFpNm8a_-g2ys1+|xmYVaY6$qs6CS?q?D`fF>! zoi&(#Y`?$%E!jPNdq?c(H9}9g^_qsaXL^)qIDRo-;wmz@Se1_L)>oIF;iVE?H0ZTN z4RHDU>!aG^zN}*Y23j0Lab$UM{g{0NCv})|bd3gOf%-(jJ;ZQ+&St3p8h9MeD32pC ziyUH5VLR_4^$^Ud-PVg2Ea8#Ev~g2mM8TOq|x?N>#A2H||X*tb)5^lCr=0m@RW@3Jd=&-CPp+d+plw3D*3d zEWE?2|x$$J1G-+prI~n zasUBGR5}5cn98mp?7Benaoal&%ck zs%ncVA}IR?IjTpX-nkl@w}%P#&@p55Q&Lc_QeI5-8fPkaL1_BnbT4_bTxI$kYB}tVD>)4`Rc>VgBEGH)?^T>ScwYxzCxPLugF_*NgBUSie>D!b&JI+S*$ zW8}daupL20rW`wPNO>!YKy8fR<5DD|RnZ z(zX&5YkX|gXhtsMOoiUNFS}t@Hb>f;LH@#?iQJ_-eoEbDMrdYRp1>4o{`7nZwjJYV zh51=nT8{2-Ezu69$f4N59@LJkRTygUoTB0I;eK1(#`DQJd=Bij^UB@M$9i~S%@C$b zAs_>&qpsIz9)2ZG&B=PdgGY{xHn^hGW?xfLP7-kE4x6+AY3l*w=+-|j1Hzjw&6Xgy zUOkqK2{X7AsVPM*FoF8PX62fYhRLn35dB|=E7)!6-7n1AcQ9w))v~$u;}RomDx$p) z$HVXuYWDJdrvsV9AzAI>^I~{4`pr%_E$nRHQPPe-IykOE;5ovj=i}-t&XB_%UfDjK zjIoCjD&pxZ#OZtGRlP(3!30BZ(j&lQEe_+TfAbQbwf8o$wCpbkHwMokh6%h#r~YwY z;3A;Xf&X+LIC_&UuCv!^_S0$oPx-0Dl=$p4oWo$Q82$`jlM0(dEEBh8b_vQ@sE7vL z7N@4BQprq0FnIv%jgJoHedbG7IzWG>54G^LcSE@J53^9_iuL=|-`-~PHh_<%qu#Ws zSB-swaj)l%C;#v##|Vf+W!8{*2ou9))E`nSTFa zs+p2tlAlVh{!VE$^D*7$sJ6A;ApgzD%)4EGccuj)6*jHcU+&o{a2P}ga%eV}xw!@N zlf+~IZeXS7#9zR3LNUY!*Hsx&M+LlCy8B4!t13>1>(Bk0Hf$&YWW%Q4RcMkLFPkRn zAflvu63n%%H{!T6=U&DbAm;w>DIS+1=k=R|2!Tcw?`1%UL81|z^5YTv$s~bf&NS(a zL6SkCdzDlhtTlzja)ZIQRGUNxD{7<@Cr${Z4L->GzJ2oyP71DlG%ytAs2y~!l{s943 zslYo;4`ePNDo_&@8CHnM@Hil5>0XQ2Ya|Dvq1IBh(woVm9V%I$7H~`lpuo60t*}5n zv2AIrc1?Rhv!Qw-bFm|Zo~T`E_;eG0>8`ol%) z5zFe6$MJ37PH3zO3k&UlDt%f76n#hT)oPhpxl`K7$M>8MT){|AK8=cJQ3(BF=oEGc~0-gF4Ae1%k@ncKYC z=2DY-hew0MLrT>TCN%3)O^gJS`+D?e#&rT+jmxtbdFTZW$us%TVkX=5bz=&Z2BflH z-VMIQ#^68qMI%Ht8`fsE0Jp>U;;ike zG@J!&r~F{s2FAnp+p-u^>Qc3TohBm(WVVm|A)QhAmYGaCyTh}zJhG2A)-ISaW%Bo` z4T_VQmtxA_wZntJqY4r|t=-BbNcChzk5I9~1D=W zgSU|nVor3HEl~YH*J)z$TzT~9d|;kVA2^9cz)+caN%Sgb`Hcd&cK@8c@q;y3eZHOM zfD%weHTRks!FSms`+^_jBe6_eKPq)WvSnp$`T-@)HKcp#ggDV?OSlPG(sgAkS9k<` zQLq;a*FQx0^=-!}_GFT0S?lnA*q3`hM=K^^_fEs%kv@U5PVSGDS>k;0%rC(AhN^Ii ze`sOVT~1Bv-fd{beI^qUn=U}h6zl0cY>BOcl2OxXJ59RwKdBVzVcF`B{^Ro7{`|(Q zTz>jLGAcF+UwW#DijoanXB!2CE)wQ(zuLOGy0Glp6`S|yO{K;WhZ`tQW7>4LY7I8c zb^>X=&V%lWA4khpULFo_0kE{~Ib8$o=hnrn^tWQ2(6e8l$DKPofa<#b-3atu zo1ZzIEZHZ%Rfq6NMycDHTrS$Es6J5e#-Jr@npl*Z+U9Tc2*#v@8`6S`EeHH5w;(^+ zjDvY!RDW4vv%W=q)2g&%Z#ss@pEM$Ec-f9;m4O66L8ogYxI7!=7g<*KYbFRcXl_zR zqQG%f{kmFc83I0eSy@`zv55x0QF(TI{HhGo!8@6po#d$X+qkdSUc-A5A)lW#0RTRD z*WdA9x9ZIDZ$D=AG2SvjxD;w;p#Ke!+8xnZQl*PvO}SU}?%C5$=E_#vW3jL`F=09N zpo;^1j+)s|Z{JRNqS-q_$@q#LbEB9|+wm(AN5XD!B|KrB#Y?ABc$dY0TJmxDn%u8{ z9$xzL?MUkH9yL*p8S^{)6dvw8%i1t0#@TS{%BtB5vXT_%z7Nx?JZv3V-8yw{)uOob z8ZKkU6giAtIW^S4t(VWx12^*SehQiMdc~8;H&<;s{OZfL+{+4Y?i|~aar?=qsc*Jk z8Pc^wDlN6#sv$@P_ALszLSiAV?x7AloPF1xX`$fb863q>gq$V~wj?}r8jmpu5+rka zUtMa4a#WluQ6o~&hj*=R)_qDX-@q^%ho)KI&5#6FClb zT4w27(20BpP49d6j7fbAXNcf^yiKfe>QtJfVZ7UJG+9^g7aOG3RHlX83Vc|!1@2t) zyM@YU3AGVRy$`?Qs`+Snca%&YFb=beilq5&LO#tsVk#>o&LBLH;T#mVv%;?we-E9W ztNNWgfBrgg^RISn^X9$NMv_6PHgstg#^Z^vqpLfN_TbH|w`SY?bLV3KX8Gnft_z*q z{FbgAaX#6iM1GadYrhFLBi+d{GZ!NcGg6mvWAnT{*IUUAh-(J>12Qk3!fnyxFI9K7 z|89jyrMsIRDDSn@yF$qyXcRe2#%5)tyyc*Dr|jzGV{FpxeXG6aaWNKsw^@~Dv2^K) zMQ`rC`u?sZw0_pAM;<8hxSak16{2^lNEiZm4p=?=W!+w=3}QipFy*pG9vO3`)YIgi zFyhP)bgBlMEfh8(6i_bf2P@f9vq?Jv3gpVPeTrD-o<^}6!<=CWdkhe}> zWn2VPGo^QHxSYeyPWAIw-QAxzGa&IV4|gLh&*QG=h$Jt>oT!CfWo6ZTiZsna{D zkR8s`Fsv(u>_yh@QvPW|uXg3D!nx*isK&EYR1ZZ&?4|FWd1;#A@!LDkM;y9sg=wwx zfbhQ7GJI#j5e~eco)LQKD>Xeluwn=q<^lmPW0j*21d6~9lAgLQ{1?!MpwU4znFMlZ zllh4c>iVlg1-)!mG0SYnjoh!emZm$V^kTEsGzSBF&2{9wAh4ph8mENUXhFkXjE7=> z%q+_D6Ky30rS@0Ru|UZ;DNA+BH^DX`#b+4b8-BntAY+#yFM+5;?T+QU*Ms%2-3s7) zmE&hd`t#;p=LU5%&6 z*WZeaM3qJ2UUtyq=-BkSDqNWlt%_37-?b$>=)KS(X3``-R&Xd68*8g%MlHRvZL08m zVk?RiVXy#S`bIjIm$xTA7XWXSr0~lZf2uGzXLH4ha~@W$3^dC(rb~4)EW=H7ra>c- zqd(hI(6clX`F%H)F{Oq7);Jv^jF@x9f7cu z+T<*@ZDB0}?IUN=p(pA6!jVmM!ZD>Uru}MOxGUHq2)`wfs$^Xl+7@D`mUXegi@K@& z$_2{52J_Xk7Lp}|X#q}=LqxiT7_L_B8_(EBc(~fkiv&p^6u%(y$$UStVA1<$xdR~f zyM(Y8=Z^BCc|v^4aq$U#LHjS(x0bQi47VH!QDm=+SO!fp)h5S>hm-JhO&!)%D_^Xy z*mtq+%e&QjGMA7YgB=-u_=6;%eDkWmO9cB z0khOnP8LvANUTiM#OHMa$wEIvNA65fV;URolEE-gvq8Bxb!64AzzlPDY2++T3Gf7bW+6>hSptkpY(k|G|f} zowq(Jof$F)KL0g_bYe$j;vUn~ZJ>8-!k zbGaB5vBI#c^Ud(P`X`xsl*C;~V*)i+%+x@pu3?q}{tA-ck-O^0FR|lxy8Ow5IH|^Vidy)FFds+4oey~E}J_cF(s zEF^49${VUIXa&`_|Ng0N-X}F7p)>8oUy=i?LWn39riD4 z9OcwW^u$c!qjPBskQwaKzwYQPHcoB}1~GVx{Lt>v13)-G(CSF%-F36|3sO~#A|&dW z#VJ11K%Bl`SJKAQYlPLIZyeGRgYu3~p2soFKah1z1X7lCG zbHu!n=ZlM#E|+})KAf;!Uoh5E{B6{+V@Ji@E)wnj=W(Gpb@O}YBZ+!n~f*{Aio?h_#5;6q4n;Qs$ z>8wUQ6uemW-D)GJ<;!dNDq_29d9lvLW%*8>aK#@)X|3cOC0IVbn+#VtIE?UmvEs%> zZL^lJMGGb)*ydWw9D4D^95${l4Q~@={kyPEv7`V&5G$XUBS>20H^FGARsfCay(Yh`Sud{Dsf~jrz=ab z2R!Dc@;O5a@QES8swY_;#0quH8Z9!hP_S5C)qQS0IZ~mI+9+giD(S0=81)R7cZZ)* zP*r7qs$$}G%PzWq9Hwn|azf>ML5w$NvLH95TgIj_NDzeo+tk-JHA7c=IXjG2Qpz}T zX|?0O^cnY@{Z@b~qCd$pxvt=n&aqNQb!1%lpvf|f)-f^ZWx8-7 ze4b9ev)aR)FiW+0^gc~WBQf}NAao?Cv4J_|ODYa}UAFX@w)|HAnSa${%w&S}R&Dl&EH-2%>dM?uR#pbilsqwg`#xCyBjD{&X4zY0`)l$wu2Q@2dH0W z=Lb#k>FVaCv~|=OmoEx_NaU0~u`?px5c$*4Gc?AY6XCP++Lwv3hcS>N##Yxg`7|9G#pB9zC*Zh#t*gdVkRwDg_F*Jqpu*5VJ-Q1Zg$0H+>rX z@%fbyP)G6FpkOB0xB`!+KjO2^^R=>#D^+jrAlu3s*+Oo^ib{_T!XzoHdv=L%po9_Ex%wZx zhL|P79usJ?Qu|lb4m}4C-bd9Tzd5tw94=2cI>i1rEExZFypC{tx5=C9RKX=NT4Yg= ztw&j#CVe1olp8XcaJgpGeG=(P@O+}* zi*XC{S9_DvG>*1J)+>%r@$(<1&F`v8YrIj=j?mOJ|Gt?#EIb%@MV&tKu;P)}oip@n zm%Vd6Xxa5p6Mg=|Z$Gk1S47Z#gWyGyBcwSv?J*mp}XF5kxPV^ z$&w|C-Lj#Bjhs8*7EAwgU9%Z6^&B!J1mv4S@gE4QvnX)NDZCf<2)<#54Af|9ryAWs z#_7rTp|+UZ)al>1?`{?enV)rYqWS#>d?_LE=H`^~HR?Oy>%4@^wQ%v8<(v+|=qWFD z8qL^WSji{uS?2yf1nh14pPz50`|K!v{@hk$bk9R5(n)D&G6ArPD zPyZ#Zd6XtyPL#H(`C+#e2>pzV@e+KcB>x?ECg%A|PPnB`ei%j)OjL&hiE&r*$u3Qd zE97Da2Ig32yAMO^p!9w7iC^BYSf>6T7hvj^_?FyzJt2*X7RccK_La~(%2)t&>cki3 zA2APPh4Ju$1^W$-(j`-;=&q?{;YK$~l;ij7X3h1I&Tba=Z48ULXidx`%Sm&Xwccg> zD;J@z!2^A0q~6a%^QVnzGCl?PFYG_QBpdXyk?0{)n|K3 z0uf>9!4J@FGY&-v2h80^@RSV=?G)$bLl3PcL&aJhd zI76yy*OK-5PRS9$5J~i32<8(O1Bz4U=u^gMraCxv7L*0rew{yqWye`7T%2Bop|Ig$ z+ufKae8>?mL-W2n`GY6P^FjrE?6dO-L0OqU+>MtJW!zWFWOe@H53~+PBG0`h_J%{as{Il@F@ldnfv%hD(;zW^99%0H_+5UApXKr9kzYmbc{{G0e#afR!j_cgbG3KXx2!pQ|?<<)yPTn_(mi zG6191g+KZeobe*6fz&9MJ!e`iNW&#?L@ zC0oTB#qh^xxntiZd496S)v4^D@A~0m$4XH<&17Klb}fpL;EZW^7;d1{hcuRGqR>JJCmdx*_4C{~unjT<&>C+Qe?OjtMU zYX17}uF=A_k3B?bAU48i9Xd_pN_&-Q;057O=+GG-^RIs$V->D>^&#pG?S7O(uCslkc+?G znetpB6n>pC+&D6fVpV9~On-R4uT1QNpIE9%q7*n0ehZT8a%|jkE2$#>68@>WpE-W47(c?AKG%7455!j9)fuuj@-n|I~ZB2mXii;_Sll zGIieyfbTTzVF|Ye2t;DS9nbm>^$-f75X5K9t0BMQRo_#;qAW>j+aHAeDeC8!;+vl7 zP5J+gHBO2tSDg0EEKlNY;iY}1LKJteUhQU!PBgtX(^)eW-3{y3`CwX9QcSc)pP)mf zY}~TpZBq8b^8h2{bYEZ%l9qge)wz*WVkNrA&y!ku&)GLn?dy02#&kMaR=!^myt-x% zHC?hU$X*n|aMSG^PJOS-5h7)yhz`h3#X9L`HEM3{;BaZGSwEo|)~XSEkVrO10v6gc zhgaHkG45Hh{puh&cObkmt|vi`7jJHg`P5%%(g;VEWun~Rq~w0ID{)i@dl1Hs!Z}ws zm(@K`$DuR#y4y!Ke(e+{9TfYO?`XJgU&B!4EOVk=ckEu()h&Z98dEHsFR2)=K6{o| zxs9q(Fs;iSI4$(8p)Y(MBp{azj#3aXDGZI98!-JaA}ZLwn2UV2 zU0dJFBfEU_n>&L9kV^3H)v4h6P^W|P>OsO%4$W(fVY}(yU-h9P8N#j7{`!m6j4y~8 zLdfIryk5aM2ifvroM7&;^$g`bErUO+m;UgXqk75 z$jgN;NyBMdZ}UtaS=li~Q8c-HoxvbhB_V?b4h)UDl${+7JeFXYKAapC`iJ#`310$p zN5V|;gMV;~Epm^vKvn9VPMR%jd4$w}TqJN55qSafx>a15L~Axi&G-UWPw-HpqEx+P zTPXvN;P%0OExWU7r15zl-Y;0be59}@pwKv6eHT)Y^2O~*4>`NN`t>vaxKyg-AP326 zYfcc2iy+{}wb+B}q{G10x1E1e-Pg~r7Fv_Vv!Q(RVlW43(i+0G4IKOH& z3`pw_A3)Xzqppko`Q*->d8cF2UoYpLWxK!VAe?zf$tOXZC8zJ<=(4FM#$%_QVFnvW z%Ui%21zXA~iWGn0ELcRrx+{QK!tpsN*Zdu1*p470W1G~dxxT$U6=Lx7=$#uk^6^$M zeE){*#HjzALa7sKN#_>B*?aj?%rX@kqJo5oUEoGfZV4I)Z^-i9r?Zv+*%2sw-z?l0 zZr(Ln^slxyNsiSW`JNa2})!pl`T6Mx#XgQa) zA{8-__EcliV_l%T3VcR5)P$V}^1a4> z?HWTV4xkuU?`rw3zEFA6h-;VD)df`lnofYni+@7B#2Q$QdUCcmvxU}?YpmaPX<35# z2*>{Mw#}=S1%xl{b>Jf%$*yr70XBNSe~cJt>;}~**aJwo5i#$(3NA{~>8Nc=mIiMz z&IZ-Nrf_!LFTrzXMTE00qoCf#;ZsoB5h-Zz0MBW8m!viIsgH@haugaSTA%!cm%~a6 zB2ItyZ7_d|C= z5PLVdx+4;o2oRy*{{KE>>{kgXoEd2v?$!-G`o1N>qu!^awa8NPF;b z_4ziYbL*}W2O81pE$lISEH#7H*Pb^zp#on^G8F(DbABN`XETtn{_K1>HV?TU=!`{# zxtQ5CviLjn$mmy;@;>JUL3QeVK5R0}XEgdvUmAN{_1UQhEcAf#Jol`s3*uOTPGSP4 zhE`RG@ybG|3Do;Ufl3JOFs)LT!4r9E0->QGWhcSu`?fs}oX2EF>DvJ17Kr%nGQ0fS zN!lp*?%%)PbmHv+rl3Y}Vy$Z(O18Xn&cIRZR@vLNw22Z+S1sDU+4dg(7NRE;qPq1X z-=a6cK`u7>7vNc?XAt}21pX*ila$94dZyo6!(A=8>!dEn)&em|?Amf!d{B)trSq&i z32RqTT>OtHr=2r`0Buh3>S>iG_;nw2#-mpYQw|)KXP$oJB1BOKzZzc$zYtSZF%tJt z_Ko~K&aj@ar1weyB|=<)bD;W4XK#?2OFK& z!?Bm4m=Yg*=z&5K2S)XQ+!wL90*g`V+kZE_3w5rQ1$OwxXnN~M09pT!=}KB;Y2n;d z_7S98Fe4eHn+{0GRbn`*5wRyKY6ubK^m+fc96NXJkic}b*9`u|TB><>Pkk<81Os=m zkTw-#K>8$?BgUqE69XiYCV&R%zZp!UQ=c&^*5j@tEN2HV22xT>*@UhE*`9ZewrNjvnU}l! zc=Apo-^g{tZyuIC;xy|AI}&MCc(Z3RG|~acg)UQ1f3l#*QuY1~=7K^|3}NOh+XaGK z)b1x)^dN39f9nV`9XivF0b5U`UsVsFMk`22+Qf##s(8V#nONT+!z8zgFMaCIz{?Z| zy)kB*7zlA!H&iDluy9g^>d62N)yhBqu5_Gj6vb5>BJxiI=}YyA`g`Cu6V-Fnn! zsL@KoP=;77DH_#j;v$Ja!N^<~ zXpsZ}K_wv7jK3uBjPWA(QQ-EOC)`Hd3k(RDwR&~xXAi^glP_-jrA_8OQ_(_m-4L37 z=tggrYT6;hRC4>2_apVrO+O>hRD=A$);=DEJO1+?RX&{akB#zHBY=HgLNCexnMn6u z_J3^q|9|{P>1tow`THlVG9BF)D~stcQlnQH?j{%{WimDECWeSNuRHDorU)RtCuNFWj5Y7&NzY-1i)}R}cS*#J z;QDw6)K{rm?IXX4QnKW3N^-K0zk#gwcax@F(nSvX*#j_J!>zjSqD71J2J9Mx09$&1 zmNWoviI(B!A(qIENDpy8pCGdXbC~e%Q;#rJ>1^c%w6H4Z1uK@1*%SmygN6C2iu({h z4h0tUPPgZWHH9ZS#L}cp08{y|K9j(i>2nsYoD8=XW<{`;I55|zqKhr zp#Z{@V*Mq&>Zz;q6uhszr(y;H(ek`PdEWTt%Y=rn4UF-qZ4Sn+!PLUl^^3tz+LU<^ ztHw+xAn)YPp4?m>}v&-jn57yY|#E@n1b0?CpDUZpdrG5TJIsfDBa2TQD{0NGqx*cP-iK z+*zvou=C(j*KXgwJ^IZ93%r;V9)HOK-MEZr%)w^kSg9`;2hCSd8?(G60J@^V{fWK8 z#=To)AdI*4h6JkcNyWm4<31_j$Kh<#KlD@ZQC~p|i^#O+ROEKcY;wJ-M!y7!>yv3! z@ne7Jgo}rpy`w+xLvH#6tK+@R=R>Kn(&a6U2 zF>S^FAaq^FC-^4hl=Qx7A%TZC?iY3TitT%?^yBp~%j?82ZdbF@y!wT-NNHa^mKy)F z_*q!i)RB(bqI5E89qN41=9Azy(}gJxxm(JtLZD-(PAqg`Lb2#lh#Q^r+viuVSbdrq z0fW&w9~>b-pdC1V4C76Oe&9&9+aWh$x!9w`WLFYtU*hK{6xP%c0;*GMm{BLHQm=qC z;ebq2M`6AEz>j++9S&Oz^|< zvOz)akQ$Hm_RyOKmREL3$I*8!-&`OVyU0+hE3cjP^mu*C(ij)H*NrwNYJe>?6E7sh>}Z~#fu## z6!e(=4@FbYeb51V<@W~xRlN`o9l)&QX>fpm#OeFC?+vj6Z5HX^aD5TiLD~7s=g(K^ z(>3NDaReSCc)cV<`qUN)VilSBw$jTajJsgQo<+)6pvLe23F<>r9$zErh?|2DgrESR z0x0qyaoz|JeyeJ{dLUg+ulf(<8&OROC1lEB!-+V(Fi53_KDV@V9li~fD%U(bJSGIn zT@wTgOdwAc<3u!&s%b*f`yk3)0qsY7qs_HmaXRd#qquTNIua`e!-Zhta|+PKE95~> z5krY-wGi+B4vo4|l57>aQy|G`Eej0$#X=~m<8Hjtx(bh&btPL@4`azrk&q1G)g#_9 z3doraxrumc&7_`0y|#lDh*z{jUJ7y?h3?ey?X-4fIkvNN*41@I5;W}FZ|DW(M%#Ig zIy@?@+wBTX4!?)dDgkgg7f9QVJ~qoYcUx{-3Xyf_()szr=lqsLLdDL9NtqE%H} zcC}M7Fy5y;e1e5^xrPCTq}=0D?5L!loz~()?-7XB`sZRRt8kLHM`+_Cc7Dv}e-d3r z8r4A~@X=b*TP2r;Ya4yE7D_}2W(j%?4;EQW>UFvs3$ciIeVyj2iWCce!Z)lQ(%IQF({ zC$uS`?rUG&=oMDX!W3a%CY*-FEbq`ryPiKbSK<7ZHmxY2IUsknzT$)leT2CopNH0J zpXmzcjvYEQGG$8g?zREK9X;S#Hp+|kQ6oW{svfkaM>}kJdjQ_<9%&pY29T0%XZ&wG zA)g;wjJA^oL&+-{L?>p8WWsvRY|NS#&^@8DfG%T1h`XN+ntF^Mk9Ek z2HwQi&~qJ7FkPdCY@)~RI`{&$QdU}*i})m}TiQl)4tp|tJK54<8e>*l@2_E>JphBn z(4N-8Ct6B1ZMF2o znsUt&l~oQ7yO?MTNv!3$<-u=1d8Ts!#yo!slq;lSLLDW_av9rRy&@*7j8QmuI5gDp z-7rh#bh*LpJlmQR%Zu&1+W#;vte!j3-tZ4HKp!fGK*`0zq-hols^4}C6ANQdIa>$6 zA@OFzoqKzQGwE6to(i@PV)xex8m^qnt`ziUF95!KS2|~8Wfc)|gb97yWrl}>_Q1HG zRw|)9+<#q)f&A~>t%{SndAPSHd(jL0q;R*_!82t41PDX4ewx@8$lE(Q{=LQN> z0Qf%8nfiI(g54wBn}x9f{I-0`6fEkb+cxY?v^p0b?M9LmleEO?fIR#=sp`0;^Q^Hx z#aioJVX~Tbz5x0`3U8-43X^WO&&-Hm$g>zve^Z9NyPu4Ecnk;njL5 zJbcCerk;r&z;xv;6u@j0y7BgLUGyrkbZOhoiloW=C3-x;6aC4AcejkuJAsz zzmCen4S>HSBSdpY3qG17obOKocD=rSIx$rkLWyb}TX5C3BS0b?@_|I=JwQ(s?k@l@ zn1u!|FTH0$*Z{K}HrG2(Fjb{GTzOCa#O}UdCev)4w6!kwpfL|GZAvp(Oy6Aw&*>*{ z$>CT@YhI>UQ3{{57FZ5rh3M$+kNA&o$Lwz3OHSs`nXL7I;$oFy{9wI^3YhBb{xfT~ zv{IGHA>sC3I24lPyfpP?p>1^p}GOf>fl%Vxy+07J5 z@?Y1eh~iGm>PC`i-6=l&?{p8-Qwetv@f)Wm&Q;Z&V=r_+?;()9;HOYo>{qB^?0FR& zqo=(5nTk9zCT}dZ^3l=oU)(;S*^ADlJC50_A)Nv@fc|V32(VblM!qPY>SCH;me`xr zBGmWMctz4K6)_I?<%$Xlv7TJVF8D{s7d}WsJATxB$&?Ib>FgV?AluxIzY&)%h>#zQ zq?an^l-F%J1Y;EK+nK4`m}&|QOT)HtMMS;I!MG^*B(|@rYzoz7i-XU3)&V$x#9BW+ z84?^UxM49Se_q?;-w-EhBZkZfI~L9K*Ty3jm$n|_a5Ee!kiM71YMJQPNmcbc53a$8 zxi|Xs@9z(UO*5BV>2yp~MaH{&v=hY6x1NUTSYn(yZa9eeMKEZXGTF%Q3yI{g=aUBy z!rRN%2W`prFNtPtL#U^XvXr%ziJ4jK#*IBxT?RbOUVAOUK`F2>BZ2QILzo)3cI0a( zhzi2hy_4)GD#VdDl@k&fNuJ_;_-v#qMY|@+1gU^W)vfnmQGh7B1bG~~mwjVR0XrRi zioH>~9X#yKpsJ9LSvTZ1c7(!z1GG#+ZYFCoNk+bcmMsXqa%Qt<%@T?>eXWhdhcA2~ z9p0W2fL>i!vi`Rp zXo0zg8)Zk*fDe6<*vLxHD8LlVFqeMcbN~Nv0SLBZpdAVagS{W0x3}vxVLwdWNp!qo zS16fXM(8T>>9TVh_rco;=G*@OvSJ=WPf&DG%J}cyTfk5uNPTuCpb_eqWxO*V2bwQY z&3!%sY6lo>IPUWg>IkvL=3uZ~JHg}Ipf0nJXg!-_@M^EEzhD+fN0WiXLYu@rC(lv% z^UMcPNdI5#y=gR-?f*BbfkKfYLxwUXB9#V0gCT^>Gno=Hr9=`EnLMla=<$IsPc1xLG21jQzAQ6hu3_+}tZlV|* zQ7)F6Xm?DXrr1bBQ-xx<&B{US9vBoR_IDj~F&Uyn1*b`$Yy^+VhaK-8Q>ekiIyuz8 z0UQBeNG53XiYc;L)ZW?ucHA0(;+xnSv>nk5v2(w6 zjc6s#;W=n^1Q)(+Uj437@fosUNNL|-dO8Jhrcsv_tN;sWh7cl<$@1MPa<<}O*5C&Y zz5hIS0Avqf7&d~8eAm)LwgW~&q1HL#+%kQfNdAThusilNmNdbPAxiqxMW=epK!FmR zCZE4J+JZiJeSPkjP}BYM@M*&JM_xk5e$*S)v(2&xeveE9(P6fFdj2L6ddKbH#LmFJ zR>*9;H|#7<)F`m2Fz}D7+|Ce1`=Fm&kcYTvD0V-KpQ^JV!+m;g@4 zz9styux9xkKe9Ym<}ZMPYQ6jlse&1{ydc32y8Xy`eud3H@e{`}aq)iK5~e0|YXPJo zHE5|sB>)6`=8@iGz_E#wsvJ{J9%kRf*)Tbw9jga;QLE>t3mki2r*W=jh|=6t(y+^A z=ImQ%4k>r7yTWu7XmKYO@ZUhPd(>m@=}wC;FdruTs^CpADCvlobMsznR9g*UQc_Zc zCqW1_!}~$a^3BW3tEUNlQBv|<%hw=V07=73^`P1~1NhF|@qCYF)*X&xpTCc^oyIvV zCi2nF2Gj*L0J?@p(5_4J+9oUe&3g@VxZg`i27m&;k9acp=yRboBz|iMOr=WKAx1`+ zx7m96YcYzB2>W^$>?VO1_lT;2EaIJ3$LqNPT6S;)P(5kcBpkiOjX*#Pc4>W`p-#=4 zH}~}37%Kfo2)3mD{*&-20f+e$U5~UJ{a!vPGQ{fDUWmNT$^RT$i3! zbjBu&3OUP4(i+7$QR3x64BMO6W}tCOgl5S6BkJ!!ERnc1c&FSBX)hr?{s3m1wwefE zn7&wGnI~QUOr&s;3V}=^eb?^Qd3`X=(Zf@|x`Ly7w2VGv(+CcUjS%m#SsX9?AWJJB z&5M)^iR#-AA9RadFx7zaZS2X~SO@~lm8n1rUaT)DG<18b(m25M`jkdk@Bv|Ar*W@{ z=NdWWF%mFXT?+W_HTTQ17BQCy`4G9WmX?;*>cp)emJlcv>Cs?*PSEm%iyZOGA`mZA z?zwyy?{xGpe8%s^Y$V|ZG88F0)s6vUN#+e0m;_T@{FZc-hmcm~!uA0*^;6jxF_e^% z&F;onE_y}Q;q~{KBj)kux3ExD7k|DoPmL2LNPi8R!?6S5dkrhTNI&xZ{2-qL~aZ9eJI3AIRyRsq7Y&2}X8;UdoeCnAGgUvOVK& zaydFO@)pLBm^~AG5o$#8>d4gn0n-thSOLSI_7OyFcIsC!;T!}@F6a^SzO_#xNO(br zX6%;rgtQHga1KzmWf5#P9k!AoZ2@8_j^i?jlS|seEtAp%$zcdS^8ontJ{Cw)-VoFk zJNrAIC z=$8Y{09muU@C`xs-0b~CENyQx5+C0O57>vVU-~aD9e09gbffhi5FD&odevPY$u+A} zxFT+yHxWX>!X{eLK*^)FCpX>!XQ753s>Y%_y27OJCw?aX=^$Xm z5qD2mr^Gc)RN{URKtE*W)c?5Qa6hC(L+Ucj)*xA*{3R-E5yJ`=Wy4{$$xmhI8+Dl`~-sm1<*&mJ!ugZEEr=bio4 zjRbsxdd;@$0vL6Xu#~d5M>SCMYrH@1t5)mOtIt}WH24T?|uNF~oP};v(uax(zw<y$(YlPbT z8}CMCkWiA5P5jSn0^?zU36__orBSiAzcC=HLHN!2WEHyTt27At3gQ~(?w$l~Y)mf> zlVJw7I$#P-eR6szdILyl{7})j&}uW<8;>uq5)LaW6H$1iM1AE%zCv1B(@6!X)X3qm z{$yvfpP^ocT7|@F2;&B0(ikQ>lUuK-#)zK$^k%p0`DT>0;HC>(lL`hCn4%}qe1{kB zFhARns_bC#{|Zp^g>rX6cYVF$umy(nx`YfVY8BRM;&vE5e<+oW4?&!qvQcK@77E-|`A@<{db_j&YFY@?MThM`dmSHg7%0yWU%>l8>BjUF=*AYH&4XbLM8IvxXo^&0G`FHG%J~~> zlO<`-BhUz{Xwu)A!&oFxOX2TbRsMF1H1rjTH7-$U!pQ7^4;E?gXYV+`^KgKH<(2eF z8jY=C<%&j5IH-d!vvGm-*wwv&%Lu;ZcSjQfY2|h##!mqN`qD0=6^FVpOX@yA;;5J_ zs;g1qbN{Z{OoM(_N+e-oA%MxO%(oCS5tA1MWQOEVX`Hup zP+3@y?Yhf)?DDrwBynwK9P8=nAtHg3+`$vrW|LSSS(5z!aet~HbdSD1A`Te9Z=w%s zzjI(OXovrluo1F-UK6-5o(2(bG-{BGxCkOC+NyG1s|29ALKlH`m!g9?18Vj=S6}0_ z-6kcN^;P)n^CNbpT~E~IL2u)tgr z4o?|>J&?Q!rs@GW`p-KY*#bI-K6U=LZxe|JB8G*9BnR`Ug;H~;Vd zD{2XdD$&s|ZV(;Yr41>7bw zfGjH&wf?vhC(8zc3;1q?4PAvU$oij1|A`7NmSsMqZ7Qdb<^UJ4fsjEl+B_uD-iYVw zi|}N-EGg&(KAh9^kRtLv0(WXKZ2cs27Fm->^Iir9i)F%-QC82+%Bo5)pN!WjVhA;Et?^DSuMD5AJ_p@ z9fdch260R8@7(kA2vjh@yyxIGnjti};{PlS!|{NFSVE&TA!3ji$Rl=Mopr>BD;Os= z(O|-a?i70cs5gCa0BGeHXlK19buPvl768$GDYDrH5>PW*HX)A;L4_p>ZW!l7Md7v% za)HQ42>~hRQ!x)rLm$IDWdMUQb44NU)z2 z-2iICHo7;a5SceHe4Hc$BP?#I>BZ&!zO7e;hL|%}ISmbk`o=U~ zzpk^1)p^@AILj+kM-%&Xtel=gEyp>$xmI(#l**oYsfzlOYf3G?t{Q9ZKJtOaCYEoN z?!{5&%J-kGWdtLY7NxFSQL89rU***JTH~F?uFzO?O3xRCYwXdZJ02Zsm^nTsN?9M^80gLT7!YNNLPA{^hh9z;|Tj1RCk&jt;2Q!Pk^0#kO2Zi=_X-v;r zH+-~f&3#{SPFMU|>bc-e(;wur_=jg_?>2}a8#v!pC?@ny?}yfa1^=do+qx)CXRN$O_H)@@wrT#9dMwi?dNP6ZAB1d?+lL~h8HPKM| zjTNr%_$oNZnUQ{HuaM=){bjqyets1zt1&B7PhU%R2Y>AHKXSJbhm^aLazX7ci>nR# zd-gn7XxW~=QY6<`SGqLFrN{T|iLqc3m#{r;yl&dXO;mjB9+*PC8? z9SS@4y~U@XQ5Jwet@}|%H$z)-9}n0*)gGVJH#7=5|KJ~X(30A1IQP~ANg=y7vvQqO z8k*K@9ben1U{jvOb06==*cqEw8t?D@Sy(sJAeS*{HGeS&dY%CR4F{-uo0s9u;)<3IiTKs%;CS5qU&gQeDY|6UVH1BTT;)sqQYlb0K z2owUP#-geUjZN^yrC%*=hh@Apmz*^;aV zDM*ehy-oU4aB123<6F%|ilG^IzZfZgTEoAc(GtN|=dy+4t}&nBH};VY(Y`CBc2eZ0 zonhj9G*6_gPAF3qcgkk5BfnbmQYt@9#Xc=uN>P)Q)uMB2-{X5h<@am#?C=YuIjN!{ort8%7%`uwOL9cL+j*Cv+m3JSD&x@#rGuPtJ_>F+mT=OlX1G@A+i}= zg~co9URYZM%y&{{j(!WLG2+@RMbq4)yE^eT#4XlDt|u@+xn_1|?W@JKYuQ_pra|V~ zzLhijb#~Tj`Q;g!;vj7&jh&Crh*(7x`2LzWcAL6H<5PCO)`F7!m$!@=M&Yi;Z#`JD z4u5E2yqJ;}UwNhA(Gy+u$Fa{>@>s9U>r`-2v1-I><~TE0g}yyJBMNbFWzoJ*r!@BX ziP+nFe+$`kq;u$-f$wY_bKf7nnd4~}T`j-Q-&`5#*pm7+qTu-sW>Y8T7G~Ffo4H_p zc6F*>T|J-I%N_>}irpKk->-;WRGYpx8+qX~R>IPGRq`-o&oitO5q=}J_nNr+!?D}F zXNCgS0)o)et~U@D$k)Xblz`NKXo5-cHbM_ z<^pA_oO5PovDtrm69q+Rw(sLFy#K(K^7_k9UeO$@VP%VSVNaA7H>^C2h`p!ClXBO0 z%f>R@Jw8_~M+NnsKFtl%Yu7e$X=l3DYnUX|8JI>N{}EhsYU81( zyLS&Lra`6b?%w(bqrgxpzfSlu@JaHZ_{e)p=WktSOB0-g#28-jQ^gC_gr<+5qrWN4 znxgZwYI@znlee9iRy_keLa3v6bQBdIRW9)qPp_Etl%Jcn=nuMeqlC@+P4~9w00n19 z&#?}_UzPRln|{?VyZ8jwZ@MR*xzh zFbN1o@f>PC!b53AJ2~iCclp`YLE(+M*L!!&eBUJXDdIt0j@Hu=BqSc`@BNfs%C!$Y z)mtOJ;f17**0-vS(I!-~zb`*}+m6@URT%Pn)aN*tuKBm4y>@TnF00@4$qK!m;_!1Y z{!!N)3vK#6KLOp((oQ$lGbpC2ju>p$PLJFb)FLNk{%kGtLoEX;Zv%&eGQ2dQ%`bQH z)cA<}WZ!LkjLzSz^W=okMx#C9gUN?NX*noP^ol%?kO>{#^#1q><54qy?yWK*@1Ko~ zdbC7;lx|T_%-Lo9TBO0TQaI%Wfh%%ba~jSHp({UrtOT? zA=Pi<34$`RH|xK)&Qsw+AG|-X3mQHz|FW%K*}G|wYDcL~=-WEn@;%DbUE&?y!#f^0 zPRCzXym@?I*oo(Vt-3neeT(a@GPJ!V-UW%CU*1yQLt&H`(f>uCub9*6hM;-Edn?Pd z12=b#7_pW;`SjwinFn|K?>A{B4-Uozu5EiAsl-^KPVLksEN|`clrQk>+VP`99T$2- ztCf`59K3y^w?03AEaRz1VSy5>Qsc0KYTq(t_`d!9( z?9sk1;l5?%c;JIx+1$)0mpokuvUxAx3Tqcn7*pcCeeP_V4AZwIs^fbewFF6pW63-S z&h=6a8J~z}-7c5v9DVxB3>9loZhiQ#Q*y=&`^rv!``m$9k#fnu{jkB3f`8MYq!&83 zjZeSYceg+Hn(L@uW}uO|Oa6u5U-T(|#cvegkc^7jq^KlS@GfAoaytv7-g}d)c?YLY z-SnZ3j?cYUa)j5dFD2soSB80(Z6fr0hAQr;Q)#uobrP%d+BeAMsp&t*(sSjgx#;IS z!zZ~>iBIHjPYa6Y8b|L^rdBS?IUiLjn0K#VBm6hLVcE&sLN(4sX9q(zG0gkd(Ac?N zk}$iqygQ1e=fg&k{m$L2N908=S%}NJgluY7f7QNonu*`YM9psvz3S)W%g1P6ckTW4 zdAv*_ZvW*6oYeWki)*cmTPJEX(xVe~W_#Z}(J)NV8#WG4{vPM#)0}DFXlQq%-tUGk z+k@8`PBBjPZvHZ|(pnn)bu)sqB72!>hXwXT^7v`?7@3Q$>0KRPQGU=RMDu5&L0yFV zoLSW{pEiqVQUb%n1Ml<-<%U1AyXi_DkO}iV`MURWD!2IO2`Aw_hvF!1sZ*8Zd$up$ z9(t1U?q$@cQe8&T_%(90g4V9%**}htr%4Ik;Tr#{LnSNwt7A6!0&_fvf0fh3PouhV znzQX;US-C9oE-1evQ8C7&VJm(rzEtte4#;DN?AnrMOsCzp%9m%;?ussScxTP)xdST zqvnPmxVp|v?bo@(X8Ok4f&Y!1e=VhBqvvWKyZY0$|APf^y{KxqTPB-d%;fBaqGt!# z#Rq#5=AyPzN~BPSt*83ImYaR$AZHBkik3zy_feC_Qg!#fT)q?S8KE=kQfe4=&9QhS zW-HGEYk2K5BRW=@Ok@9j0dp2o@< z8Tb3m!`Ie1GakLt@GM$fywWFpo$~h>2YDqQ%IS&;4{8bFJ^e4fS9Rok^OQ&{ig%*? zaj$CQp;>Pp=d?3vtoD({tH-9U@_LMzP0&4^Il;miSEVj;y5NJ5`q;pV;i;A79JaD! z`Ffkf_lfpzRWvGMx{<8%z4NQ@hTWs;7w&e)aBgTFvlNZL8Q>(u-FBj+Bgl#A)Yb3O zceQNJE1bC}K{FNGR6&2ed*W-CmquXc!j9(LkjaqtiJZe+* zm~(}-sl-5}F0{I*$?fRfoHrRa`>Olx6gDpBjx|M0E0TTpVp=I*0aV@(R`T1R_VWwpw0<{$jf zlU{v2i+y5ocKL(gyE4s@>bIv(UwyPy_o3b4)k!g_zX@6c_ip9L=-kMYFBh?ronBBG z!e$UYVR`g{r+{CB#Qy6qTt#_=>wSiF5|=Lz)V^BYd`v1Y?%IOSTJdPc@1GxCt_yto zJSeaJ;jeRF%WkngJoql=@Ta~>gR9)SH-p&VNLh3n8d&$3FQ(B z)0Rh~U57qzDQam5T)SqQ2vNX(`Hka!=J2yJYJr$Vyurs7|$g#FB#ryU=r+4|1Qfm(zK{N}aEBPrJiwCA8Y?`(3M(s}9O`fs!?y zd$@2iP|EU2pPftg7sb+YV%m~hz^zQ3p{0{?&s&2!LM$=jK&1 zvwN*2lx^fJk8VjUCNE#YX7sM}PUS|A!s@B(PD@*O+^u4#evSN1|G{{_A^*1%OYP>v zL0kA3p2>F7-R`%1G}wG^<1?m*2gXjfZyOY5Y3=sCr7CeC+EH!r`Ru5H(Ma6g%essr zO-|phTZ9Rxu8lcR<-OXOadmEKct@*(jXibH)?sH>kCm@3r3#^i(H!NjHFK|qu1j1I z?dMJ)3zy!+d?LE_Dm&d}gDBe*N49Ag8r}^zuN*dC{9s%z<5$E>o4%bY_K`k+k;?rQ z*EE{wp^bq=j_h3s_}E9yBsT9dY)XgocnV;bldK( zFM@;L-ekJ&E^PU*j$+QTjO{nqrlmJJi4<0Mrhn?}yna@IgWu?9hn|P0DvdDCi&MTr zN5u3;XD)ulp+R@BZ_p)_Io0cx2z#EShX3bFH+!=bXU4T-cq5b$o>x2iBpCvNi#xQG zlJ{zhhDieiXWn*0nw6c1w?c_Eg^a#lart%c4>-?b!Np)X?eq zeuiuB?5I9FxCk@2ITe^ZjhlG^B<(I`JQ~L$uHdO zqd)jC?Blpp;u~E6PM8)4q8Prhyf+$$(YwoDR9dQmn}8Sq=sDnik&8!lNbv6E=BdfH++?$l zW0oZoSJ$22mYZdYz| zCS4RrQ&pXT9gwOignqI#RP5A2C4zNzsFbb3`^oHIpUp>2-aqH|c zeAG(I;^S;jbo3djcL>z8s(3f^-s*80EU#VQm3O2N%sMadioY&4nC9NSudfqpH%0uN z)y<0RFAT9wXQJLA`NQ0=62}xvm*&mo-4DGU*-LGqvd`I6Si58Gi|TipNbZ-<3VO^I zUTm|mx^v*MeZlXUhl)9S-Ap4mg$|3KwYw~`XR>n439;JWm*NrSDaMT{<+Sv>MisZG z?4~q{iYsWSP$+rps%&LC=n!?LbIYH12Rir4Iyy9LT!%BvsjOkD=uNPn^Q#xUTlcNL zpc@tOaN8hM$SN9RDH=<0E7SAd6V-h(Ik~Ub$ruXBxY#cToN7MAntT7{eYv(V%Ye{( ze`|-1ztlQ<*M8Mq)IKFqPNi?8v6})1K*J2l|}z+2z_F zaCxmN@&z>xl~@$*N|BFNu`*r0%5;_es_<8(GX;IcE4iyG^kWXEew}aM+KI^Xs#t6* z{n?h}vlleqh6!xF7UbG5d$QJa{p*ggg@H-^!5P)l+G624l4AN{<$2CV&z@GsC&-(A zJGzc)R*Z6qu{!b^O?rIJVP5qpgV>f&Z+kLwBBlGeSLX^NgB;Bz67sz3H>_hxW)D4*t0Pn`8?ZTIn%~vJe9ZE)-vt3S#8EgC5J!{KWb+6Eu~so z<-N*?uUy6WNG~PZk~MF$&Z@Lpw9URxkD#dN5EjhtPO3_F{oedCnR>dyIEh(wN5sV+ zmJ0VJHrg^E@Os8C#7E~{gYcxc^)(LzqfuiQdyhDX?AWQr4b-CX4)T-ohQ3N z?r#k^oOA0~Dc>|7zme?86NbC1Kd&>JyOi)F5PNtN%g3fNzu(XQ?mwBje#J5Qq9QYe z$MiF{?hc{-HL9g%3wdJ(_uF@`X5=y*?-zW*Uq^r4Sg50=YumvxLs~Xw`_f|P%wvk^8nl|N@cq^YcY zka1B^`LiPbYS-w3n{kPu=1niDLr05CL}PE#>%5FsJ6y7=>%M9H*NxPM+bL&u`L|Rl z9a(I;A{8l|6u-WbZ?~IPd&A_EM7yI#Ap4#@krqF7m{iX>Iqs@W>8Dz1XmVj_ni;jL z)pY$a*>5}B^I@AmrAc$l2@Rck-{7TcbEes{`&%y7JlNy@T)uiu-zsH*KI7Z}9JrV88&7a}0$>lZ13Qh9T zwRz6BlSWLrM8NbN+Xd@_{dvc4Ox?O3e0O=*fv&Nu1ulzfJpArQ3)+lOu1X#;^e@l3 zB0TEuyuGoe*Ur>+o5t3?)*Kh)?24H7hwEfi%`Mz@-2Bu2lx-4a&fwmFuzc>r4P(!5 zgj3Ck?%LNoe}C)dl{p0t4OxY&=7)47FXz#{3>BD4F#em~qf*x7ZNhOY>#>YMQccdA zzs<+~AUBzAaP;W+8%%urDH9SS+11xFz{JrRmp?gWklmYDmCwEy9ZW5ACxWk0<2#P_&-vSB zFJ=dX7M05bBYJ{|!%^9DvTVCWlW$$M|nNgLk=D)_Q zVIhyFI%lJi0kCa8ppJupi^Nck?WA7p&=|++^*yH&={RzJK|Ar|EjN zUBjn3Im1i6va^ERTc0r^Ia;f?C`S8>?P7(lA;-R$?OD3pC=BZKr_cfN6u(e){B;4x zPXtJX7mnnX)chQ9J9ZH0@|tjYGw6NAg@_|5tqMkTH8W7tUFOA zTYf>6BI(2%-(Z1??#~|re$P9zGqISxNcE>~InBB>y{kPdnblFsFgRyJ)zkSj+rZ0d z-x);uO^ht4C@LURojM=tEke|SBeuI!lN!IwLQy6=okwD+GV-r6si)$7@gJZb6sNRG6~v9HQY z8sS}|o?)-IGda|_IcRW+#W*og9`#!rbEwCg)-Q;EM^KRXlk;b_e3@G0**>NhQvZ0K zB|22Qz`$+$mFmjDevjAe*F*jLe@&sIXA|*it0~Gt*HG$*GqOi^7xQdj*?%cUeOgu{ zr@&nH_-fCe_;)u8pAR%GacJtih^5xuf-3)l%=P%S<~=tq-z>QuxFW>g5agdeimG5H zUP+bf{1z7}--F1JC;v!Ec+1TOe6Q7N$m9uOIsWGDfH*hXm$KK9!5qf2m44LG2`cPX zCr<4>e&_8C{ZXr>l_M?+w0G$ZZ8h^`^!$SYXcg<0s>!kIVw~+e!Ps z^fvW>CY#|3`0W3_0(~!HteK&{qAjjEMD=%V*g1cC>TrNT5Kns4QD=##$Jsnr<0Wut z2m?0AbWq+;wbaX&b?u_&q4htd7{2VgW}75e;QOQX8l~z(`te6DWLm`S`qVIi%r^oQ zc(DWieaTk&{&)2H|Mab!#s~Jj&8W^kS$@RVfX^gz*woV=%~B2j6GPkRRhkDA_qb

#I$M6QWg4$e`y_A0OLtrI?of_Rpys_V8#}6*c5aQ9 zCe$W9)lY^s_XGb7Ku=HJ@2u|EuEC)BC-8Z*3pOPhbUl~5DXRx*PMpvwpJPIDfE_5{ zYa2f%>%y=`!Yt2eg;&SOND{Rc?1rHA?(Lk$T4!5kJ#}$o{$S*g>{vWC)uv($_ex4j zYyCS^{Xk7E1&0m-MDW6E523ZWrb9*r1u#cBw`coJ=jUTp8|F)&wiA!x9c3BBYQNpj z-@3=0%@(&6ce@FMC1uY(lL_lF%fp7u(XS1mb{XRE_b|vVN1k+{%Raw*MRfgWkWb~H z?43KFJ+;H-5 z=VL2fXcfsO30fD5R?hI%x)xUg+NBaseCpt6*+<6ty?_6H>-9cmJ*0Gk&Py4jpFgoR zNyu3(C*o66YZt7hkub(ltI6%%{eyNwRG`tP)PE}i6l9M^hK9i$FS##XJWcFNhj$Vw z`4sPnjelTZa$KAk;IbV*v$8f$KJc4%{5qZBY9M)@&w1}Dqj2jMwa)s8)`;;xH6hE& z`}7Sw>N~L9^$YVY+hLt6_2NVAuwBt7Byw2VCXOe@GeGL_UA!2x79+rb8bmu!`W4`5 zG&;OGR5$=-fWFErtEg%6n(}H!m-U96&CSd)@_W!5_EVb9CkcH#XeMm%0z zR<;}xy3dl*e_Z#h+H1k9FSPVMgpqq&R##Vt?v`J@uHg(QU~0eL&krXxoV#xBq|Khy zr3)UhD){(Zyp}%q(G@I`cVqOpvOVmNtlW>RR;@s!|_D?fnrXm ze}vCt&?)u5wE%p4iA18Ni;K$-9AQ|4avIi&aE8F6k}zZrZtm_*L98zdeHaV`KG9pO ziZuuV9)v0dV1Cv}M^RW_PENZT7HYQuTb z($SHKo;{i!>{`2D=m=_U3SxGry@rEuR82`q{1=r~SQAOgXp=EP>ARINH!T6bzMsH* z;aA3X_kaJ){`Ck4!BgS*o}c{axAHn>ji?TG;cj(_qoX5Fr)zvuB8Ij-*WRv-df@5k zbT`V{00WRYxT#e1`Y)=Cgjx;WN+T$a4SO`>x_rj{s@g;tcj}KT5i;KH$tMu5)p4m3 z1ejV~f1Jj9-8*-2%>{3fIZ^8+0&xzkondezq8$CHo9@+Dn3|d^C zqfGkj*-6-O++vf`0o)o8NXF!h1WJw+kq0nA0YAk;uPlmsl=eqQ*6v^6KBcx!wDT|x`jVl<7AZMZvksGkU zlw5r=ugjFYV^JI2#0TV|GHz2gf~LEUd|t0oF0hIXtNZ@6hIY-|PziO>=DucF7^!w0 z;Pk1Cra%WAXO5xuBiWzsR-tC&4>@lf5xhg(4eiWn0a4z*SCqT|BQfV<$F|ZFVl3TjE zjO}@IAQZ`oOfZQ4^5x6R{bexiR_%KC8Y=}JlrK%;c{7o&gSry9^*x!X=pjVb`=4t^ zi=J%PTOo`>VVpU9K{@LtK%x9M9rzhh6532t*oW$vO11v_^?_0m$n_A73foCx@b)tJ zorZ}S&)8l^!_*M*BF|aP)bd>CWtlX`Nqcw z-(S^{LTEY))qtg=@HvR@f0vyP8J>E}roY%dDj{=#KMo{shlEPxK4gYxNdG=dV^uasl4m9H7U`^bhOsUsM=T9{aGX?ozROkKx z`-;aZtb7~+2$lQHqC7_X|5i-*qAUvS zsdt7i;&X0erqHYQR-DI3D&yCb6ez1y&w3IOW!eg$GLH^NLP)FxD$@ zf@Ky9^0xPpU|tj0>0y@D_aQ6H!!g9cIPBk!G93jP0_(o(s#T?*nL#+VoVhL|`c0eb z^5vj_NWKOa*nueR%ddHE6tas{qoA<9Zcyz+d(G_E_%88mG>KJDE#5Y@2aA-g{|02B zS?4T2H+K{TksxoCWgZ5!19WZx$Y(dbf@7l5vy{4e3s&L$eV&JQJ4U1^^gU%}c`o^1 zrl!8*1|_LcRZRwWPSGwOW@LPsPt40xbS3s>i5>Hg@0M-u~Mq#ASQ` zJPK;=yc~%wb$0HGL2L459-fbQ+p{*^SQH76Kt+uZzN^>tQN7)cjnafDgv_#OQeXGI zvir`wE+O;w5a2E&fGS6HIYvLHR|gA&I*qt-s6lQvc1~L5igQUq>Co)*fNRNvMm{f& z1{dAIw*}CNs6KD&EpfR*ntx8osW2h>QC($FE$?R=k{=+_rIA?_Bg~io?g66V*q8dX!5xUpu*7?b_ksyFbkUYaJ&20SFnz9j)k0;EF9HKP~9idGUA{E z7{>{S1qBSPfFLCn;6WfLy(snYLfz&kx=MH)YozK@gVn!O_glo+WFb*Ixe$E8KeOhk z-lZHqrwe6)WkFp#E=@-r+5G}u28qMN z!|#*c_ZHd9a0FJ*l|WpiTr$3ez^=cALsuTwopmYwemg;gaaUqHQw|)`{@9KaXIppN zj|&dJ_`JG{fu6p?SwmA(Q?1yhibLUtMO1fdb8`$rV*PRuC5PRmjQk-HHS}TUUStJL zjTJX3Op%~1D~YcG%mR{g+l_S>N*M;1f5%89yR9h%y@?#7#A<>T0Ki)@aLGxFBRF~x zI3*o4!D@Q`x$u4NnKLg=)?w|B7=fk$aZ1fd1m2a+@vBz2ykkBL)`wHI4$*jgq*6qG z=+JDoA2%OgEO?~KmN7x6rD+r3D#}(t6J{!+a!f%ifxxym|H(xa+%Ea~5)e#)>1U&? zCP6<^&c@~ie2bhM-h+#1WP7Z=gg_*)SuytzY!UmW0{f;E&mHiq9`dUnfkR)^8QLn2 ztqOGKFVc?d`_pD$PvKjh`#m59DiGw}139^m;2U=B-fe{TR2=6R-{xy5GX*IPiiZ1G z_fuWleZt~I^f6)Q1hAO;tNEa!zKb8~!?+Q3xb+U)#F;{ZIK8vP#Yt`G<%<_DY{3d9 z?7#D(Z119=;zR2-O-|KZ_QYj9%FTML333uS(TV+aBBPuGmIZ&DJYlk&U_H{r${8)7E6_iu>_<}j2GH`X2DCO7O*!U4c;X`+4 zes-AAmh=2j>3B3BxTN)@#Y0(fJL!8LvA`$}m@QWhBJ+n6@B;;fBv>Xx1h99Lg|o97 zE`kzCw9{@-4$kRU4Pc73d-I^ec=qBUg(p${OGei;h14f~{)A`+wye(S2%APaqHFlo z$2^lC4mo=%W0x8`K1{h93HoN+Io6~gnq*U>c`kT z7}gplEFeobo)xFM-Wk1``xg-D~LFAML%twFb1(3tS#+unSw zV0a~^Fun#A%pc|GCCd z?%jp)OdC>TLe%FiY28f?4csH!-SL%J)IIiHvpa>ThX>gSu~1KK2D+Ot;5!sqyDjE% z$E1}yT|I{Co9ZQIR$q-5fI5lU*}O(%$YpV&Kzrbq@WTV4)witw`%^a@;|+@sf(aKtXWz5S7gvLFhMk zb%h2Do-H$3(#R^#RiJCSsSnSX$|F2!EN*Vw)k?qK{6)o1`D7$1Vw9zicLgahoRR># z5{VR&LlsJ`)p->?|J*|^-MAH)jON`rX;&0qhR7J|eMDg2d=TQ-{iu82b0gAFxd~a& zg@oAxG*{!ibNz_a8tiq@)?2*9yL~%f}>Rq*+i}L|DSY;$nRf zz2aGLV@X7!h_))0sG5hhK8=TH91H2y#PfCehuSY^2KUmZ60CG2Ma_Bq<2hPA`3Xq)7M^-)xf}#_?`C>2ZcGH16@0(e6 zHZnJlwCDZH5z~6cvmUG6Ljg#Q?q{bK+6Uex3&i_GC{@KXWO@#3qzeIn4ZnuSQ zxG2;&!)9*%G-%_Hh$nH`8P}44WNk5&F6c%Uka-Rk*-KMwB?zV(DBJQ_UteE3sAgpf z)&_dln~3;VPS6D+G6(gc{1-YXAtquZPtu|p*mH0`vK!k9w6wI2Cym5@_;6f*J6H}` zAME)!cMRtZRv5k$7!eT>v`As}*ttsZfdVi7R;YZw0>4Tl8@QFeJVyLaiDYzrjf^t? zY*-XAJzWMG>`O;Ql@%2Yl#3o74Xox9a_*4gSQpx~2hZni*}eQG7LwBDRv^^1pi6(XPxm51gZf@83WYiKiyKQ5wZ=}79;VN5Y@MWm4ZF|><-4wB13q(OE6ATdP}us3A^_MyIZA$oui77(zAIvbKtUatK zD)%Q(zkEQ-3?c?Z8>LW!J@K55s+8*5FDf}*J()L6Byj&B$8weN4Zv6WzkiQH$mLWP z9XvMVe{x)m^0;a&j=)@=E(ldS(Q-u*P9xxXu^&dU<`fMbl9`w`BOcYc;H>V3n&9W_ayT?}3Kz?@oBSS68ChHy#q5UPY zB8s6i8x1W?os?9>3c$c2ebg>Kk)2+~B1A#Mln4Cu=~I&1{b1|&JE2R$v5WTVA6a`0 z^&Wifz~=qPT8zQHw;)=OdPE7XakTaNO(8NJ$H8##%C@J!sH1+hU+=qI`|5tM` zbZ}4-jx_K6c$M(h6M3;$$BrQNi44HbCKv)3lel@#gudPtlY}K`)Dwp$2TD;Eo7dZz z894S|2D?+PO5v(nLJf^RV%V8tA~^XVg|jKi$)4@$$bu+<7})OiRSw3T(l64aG(U4S zwY0=SV|Ckv3E3gpo_jCw#On|i1t7N>jWi3{cfntNOf4EaJ9p1ze=$*ZH$nX+5$O@7 zMxyAG3gMhX1lyDHe@dy3bNDcecn3v;y`zvOUOVZe#2!B`zY z1KQ9}<;qA&jZ$P=Acb0YbJp;o#EiNWG*QnGfY4=>sKs^JB~&?zPqyEsozL79RcJiI zCJ|MjUJ$K_U-5475J*b7<)T1O#qcDE!a{(UG+ef3i$!(Y8qZy=c{&ZJCYY4Y*0^N| z1OGsQBiqF+;+jIK3{&duQI?2OK!+`m4oSI1q7n^Utn;|b`c37aMD-2>B-Qff589D? zYDajPcVyjyv6xYFUl>#xXw^5wQ5H}as=|({7bQ=sUJwC9Bw6&p6Pt2YJd3mO`u5$w zSAei^9HKoc_2*5{0!1mME<5LSrNV|`41RF3=?_7?;0S8& z=}ATXA*Ogj{0->;wy_6DeTJq|Z?+0Z-7IXqxg~?kvX!6&=>gZDe=al8ykb9#TLMRM z-qec;EG%06QL@&SpxW{+bt>BERN2wUav(eK1En}BvNrdb{9i>r8%pKy-5(R$9~8|_ z+3}`_X`Wl{tcrS&6$ho|V)A1O=-HGYoj@Asx*DBt5ok5-TviBAVo(BqxvY)I6l-LU zGlr5)K%X4iT=fVS3X&v%@8A^uexJ&B_tNFT-ovphmBJFLjiZLIthmV>^E(ja@`?^| z1L57OeXtWPXb7$k6no4T=ZL8CAfdp2aa4gMK&PQ>U7Ztj1PI6=_XGcv3Q~kS^75x6 z>u*=N5N1!}DcMhUXa4>(p@ysDar|r&kj%pG*vRH@24F)em)qdkkzQ@go@ZlgLe8H9 z3)`H$ZNBIVPejN68SP#*%B7CC`&!gfE}9Zrkn#g%TRWaR$5QbsF*!NgMq*cTswPnb zQBEn4cte(08b9U=!Z^0 zN+D5pED2hokdW;kTh9g5levH=hSaa(hZ-c-D!Lx2s|F|b4c0l7Yu0&1LZSeF` z%BX)I$Cu?eKy%@>4%K&!V?Hb&ogM#Ery1U-!Mx|1*-48{1p06^9=A@OB@pO$fcU$w zq7uSqy<=R(b1X4FBCFUBvK~v7g{Hbt`|utdzfGW>--t z39g z%~o>kPx;@uerNp39x8ffwS9kkQDAR@>4{ZM>WbI~-|wcqR}(+{OrwuwCXNJ!arKt* z76{2b%v8T1NtfL>F(6k(%1XXPXnayi&3?c~FjjM;V{}4fwW)Ez#V+Suk;`~lLl^zm zjTX&ycN>z_@7w`7lD&w5qGzM=ugp2mjq{FVQHO~=#EoT<7{wC$;Bu$jPG)xBrmqql;etw-#WV784ai#=R~740o}x@b>I;>L#+S z^%$JYTc(MPT=8F8%-pk{UgJs^7guvEI5duaX=<58L|^S{Flk~cuyvQ`_ZadaV_sJ8f&BCVnNXv`u zJ+BM)=2~@q&#)OOY_?SWldm_Yk>4YCD>mn|w2Cqd-$kYy%$o?MPjCKu`l&)}!tA<9 zv}=(nR~}Vl&xLDQVz*<`U6fZ^i6MP%vp@2Pc7-tvy%&ntT;w!dD?%=(%d8dJ41J!j zOf-J$+P|10vfGb=vA)Lgss`&_Dy6p|F?e_^<$kecturbdaxke$l#Hi}%=ySsOwRfG zC0$%|?M3_kUk0Hxl6^~&Vwas37w^=UH2tBks5F;5Qc9zLYdcD^g+DNy_+bLc*PIXKnq&O|W ze|<@@DK9Cb8cuN9+FmgEmFzUZt6vafnBpqY11 zzE(k>zgA2gaf z3m(0YCB{J)z4eIzc4ASuH}_oHGD#|LOV#KYH&x)F_&Y15ba%*_UJ>y#>&$){YtOm8 zevWIGDiV)RbaVO#s5VXN%FF9jQXkNjt~D}Tv?lc!v~50hvra)itap%bEdX*3JEuYM zIqbDWc2DV?rW2f+E8cXcH&#uKn$5-52b(WC#AvR)m)9_KC6=8yy0n`_N^N9u?Q~A7 z2^8U68+DT~cXGceFE7!z;X5}u`}O>6=}@)Sua!hi-wKm7`7tY|S@%us4ggn&zI>4V zE6k<8ljSfIFPaV478VEl)5|ti3|uyTbKlt9KsHx?Qmk67AkL|~TxjsB(CNOQTjOd; zg48Hr1V`tj?HeyG~(#2-)J=2xL9aY^;W)jMroh-LVYi@9_kIXHqbes%Y9;pvC-}v<+b|r3U@qVA~)se{cir`7*^*AI zeSX|%zwz?G_L$trOj#1shKXyRQ{%mxu||*oki}nYJL_Ie@(bX|j^?Wh4&j05IGRx7 zLp*Tpq-3BHbH35ooBh*mnmnzg*X?M$?(p||^+p78PPjM?kh{osB+c~`g+@ayv=tUN zd{~xqzpCthVe{)~;?zjpF5E90iF%?_e@ffj&(jtU(rfA>IS=@msr%O*d6%Z?x0U>c z@6Xp8Pr^BL-ju>9mQXWnr}OM-=px$_^HUa^UO5=0&i#t`RO!(6BBe*9aqtiI>;z{C zXF+nWS>8-{`g(CyX5F1s)qQ;|R@G~a2k*o`mD9}Qj%01SD=isrADt)CVshJ9o7U^j z#=0KRoqvlhPcr^4@6aLDs17#s~XKqLc+~ zxJoJ=ugX1qCEHt+-<(kTK#ld_0ZGk`tYvG~q?q~l|J3gokq$DKiH#pDy;8Hetz-X| zwDJ#wcH`v9K6OU-u^*3TiIp7(@_R3{yYy#gwuyTgGvAJBs*JIReJvd?lhYJI7eKPr z(L!dWmRepf)v!1xwr9KSEg4aH*{XM{net1&0|J?ynBBxs<^R}AFX zbsn1-sCYYKo!5+*wL+MQ)r-oKJ6T>f-4*X&fe`Un1ly;G^I; zWZW3IE+|9}p*dXkjM-LFWR;iH!SW=Ga=?yHc1Y%(xpSYbX)S$YY2pM+cJbh5Ii2b} zVqc?K_kit2+MKFLpmWzAqVL1m0%uZ!b8FTKL5nv#CD!+z8r6BY(kW6aWHYg%N9rt} zmrBsqm6(4t!#6B7 zmr~}8-9n1Wngq@6M4vU*H0?6JZG3Y1b`0mb|FVm8;9T7lcWZ4mCzqQP*CTfA4V27d`4F8KlcMmfV)C6BEQcbE&Pa)5N^UBk9xGR_Z;&*t4;snQH^B@@Nyc9qyyGrHz~3N4w5qSIn-i2{R8{GtGA;f9)$idn)~4jfvcdkynVQ3K;UP6zXu=C){pcBGhUT zee+xA?Gzo3E%)!+<&_@WSPE?PH4o`qv?dzX*z+)HtOUB9bNM}3@ZjdD)~9tvk<~VL zJ}%#`8L4+^TRdaWT{GlYGF2HXvf@8V7M$9X|Kt{U)|jRi+D65a<2}XPi-SoD;>_gK zxw)6eXKS7xh_imE?ZdLNg{zfI?03XH@s)UDfCkxqmK^EkBYU>k^^MI;PV@Zn74FD2 zb)9ooT?*Bqp;^8UN#b5KUPXNYq}H2buDT^IZP`^**_q{xU$~s=RCvZNM+lu22)7o| zCkscba6Cyt{ZEdM`$V|N`L!v|iimi=ChI94dVXnrOEukX@m0nQQaYS(I!bE1UvCV{%C2E{6CRYuNU_EZE`pvzHA zTeWLw+1VvciLrM+ZdNq6A0QXl*5=p7el&XMb9j^0k;q@yc*Jgd*#=1;^Gnn4GJiPq zH?~p6SuH8G{wA%prm}~n$7K!umim-^91C1c@;^rl#6K-boYP$=%WGTJNL5$!KhKIS z`@`3_zEWT>A!J^@_&I9RAl2=(U>ZqHa+d765IsWfD5uzhzP!R6=9L|!DPhy~FdY2P zkGU=x@bYr<$(l#-(r>yn+auyfY7Lq#->xvIlSthW^<~twV_JCYTMEyx*T7qH+s3Uz zYoYa#%j%7uQ~td(*9VQi8H||?(x+Rq#E8`2qs~xbxx4P$oc@}8Tl)wvElu(Pe{tic zdn>NFc|`v8O5w?>)Y5}Ahlrt?>r9F1EDM7GpCaTPv-1g^k$lpt8^ba+WlIT~D|6;d z)`cZwPO<*Ujbz@_Y+M{`^UR@rdA51->hDPO!O001`&@^Ea%Qhtk)18-KVNmuU9BPT zKv;Hq;7|EdSwQ8f;Q!;{Cwx{@R?lVz3=BmSDlYrBu}z_1U=`K9TSx*G-fFwcyarhft#LSGLU>)Bsp<{;DMY89UmJOZgxivI&)Xvb=vgZHP{jw*coTGKRgl*(axy*gb zq0h?;jb{=k7+M{Mj4Q6jGMS&f=Db-~EkKj~m&x(jRO$mdxn9H776 z<&FpBPWeX|WwPkj{XQ>vq@9)a^9%xPWq$Av zG!N?**@$Qk*yk{_CMMIsPh>s1nzSq^mu)M;V@B=KN}cv1yfNPO)WVv`O5~eCgU}yF zq|G8GJO2qYb(UIrLF6){7S{1rGcEqTAQ_(b=H8{g5cjSHN!3NAR%b%Tn^KSR3zvhJ ztOMF4BUfp4x;ISJ>0&n4y9^CgI-NIezhR3JPv6xQYrcNxcka5zV8FOV;`FS+DRF~z zdl&JQ0nOTt{6GGUU3~^&HuZeze`;iPwv%=S0&x(#iV~uS@lV|@d zU)@MsW`5_ju0f)6J#UxZIVzzpjxs;*mk6EomvAMjitj_I?fo zwv6aC+Vat1WO8*)^Cm+`JNfCHi^wsuK<`zO$0DP9{}NjxDLOMSxt;u(kM?&j(YVI` z^}(gqTnXk?ixr5L-VO9zYlQx`{p1q|`{7X$Di}V8vUaQt@n+;NJ@*+Q?TfZW<+X)pj(hl6|u}jbvAGK{wID$L0C9-QH_U&p*lC$mTZ5IroK@gFt_giXr8}y`a_Uq>SFQ z(I2LK$@@g2YM*6Uk<%Wnycp3?I!Ib9VZKPGcDv+!i;!Lzz{}6;g96zC>WxmW$LH<7 zsz?WN=kbOQeUmv$``qf_tuK{t@2==e*iJCs^S6SHu zTxYX$FVbBzLz+dBSH7^;$lPu{UMbY*R{eEPNK%zVG-FknG)Xp|JF$m!F)6$wHAU2M zQhnIEV7Tj;bnWyZma32x0x4j$^>%PXN+9XIhs7R^kha@bfM5v zd1bcGr#K-q;@R8CjnnC82uK!nrL@%k`Y`&R_~#4DhP341VB_dZ%Sg9xqlr#q?sL-- zbFDXK-wBQ!dC8qFh+-iRD(2Y!vaPB^H+~=cHd2zK0 z+daJ69GuzzUi!nIG&z4YWN(V2c4SI=*Eb2r+>o%h>&xb`ibQ}9kTE`MxqRgUgyY*l z4eM4!lN;UEqUOepSsy*))uc}uc^61Y9({$$MZ=4lf~VFReVACH?gSp{a_kKuh79?v z2abm>Hg=s^`~B{gag*$q+8Wix)DBKL>xKNx-7U^zclj<#h|F&so}#U}aKP{Rwl}@k z;+rO02^o9PbYb>q!q0i$F7NB~+$B<_$l0~>sY+KVhlUOZ=ih^JW5YRooT(`^Di61$ zjayCl&H1=JRO1S+pPdTmU5gt3chb{|`GEec#zTqll4 zZr*r5k zDbCwey6VUdn=q{l1`=0p%nAf1ipPqMYq<%kQXQv-PSb|wZb#44@r>NNhYx#o4_ira zo)GHCB*z zSJ(y{J#-FHiA_`{*6)5{XWU4ApN77tETJVNTu*7Vw=jOMw+tmn-=Jm4`rrgFVoNEJ9 zw*x-W)yt^l%Bg7R%rzHe&(%+pU4Kr}mr5gSrxr!G1r90c(Ws!ox-YjSBA4(zhQc3pwvbwT^DAufHO8&hY~xJ{~Q&geCI3dnmq2^ToZ z<=o5fX?!g>_}rskb6um*pkYUma>45UbY=eA*|zWYM$|Zuo)Yt~l_l;y(7PZ_I?7zL zM)NE=)T_LrscO*I`IcC{rtB;3^wfNLRu1mxHx)SoSpyF(ZdS6{t|so&bUXIOhHt^( zUFulH60_Qp@U*#YX>_a8tF*sV^dvllDyiv{-o1DuX&uZXd{*5%Q$sZ=?rG*`MOSy( zorB}tgXY>2!F;CQb8MMn7gtN)MPcqTTmsiN zpUO%4n0nC_Wy0}0+%bDdGih`ZMSc4AY>lV-4D4wMy4jkmn+;EDQT`&ZwVvw9kz2n+ z9hYd%X#Ufa`duS0?XZ9HxP0H7uGQry?;Wnr?c78d5c@^N)Bj8HO=$S~Z2BGJEE7w0 zH0e-#SN9O;pL<;N*@Ffb#yRvMF{<$2Auf@9yMiK7elWq$WpG_qmoqH}?@A!N1f$rP zm?FCJy)yP!OwWZ^3dD*@dR?N&z3mU9=u*ArXD`e}GWlNBC{lgvn#LHfJ4rolzeDNw z5%@0X+MgmE9|R|tsP#M7S#vTnpKjsKW}j0OviLPCs9;yRNr^AY={UDCC>VnplhPBtE{vAU|MWL$;6>5 zU&LgMyPh&&L(kK-<9jGwL_c#EbUR_B)Lbue=~BRbcS;+Hvj};l%8t^3WO|H|F(XxI zNo1l&(c}oJ_v}J^l3+$r$K6ZT-uwPJ$NHA$z5W6H65f$3?dqirmxRkoGIlXd`@8ee zFL*3gR8VPXrcCW*|HZVqWSPL!OQ+P%W#?UABhGu+_aoJr*D*I=Y46s4^jv>LzT|Dl zi`t~KSLAI6W;ee)q>!Dg$t~YD`+Vb$d=N_7K^CT%3eI@sh`e)8fBy;X%=U(n$}2H6 z;awAAmybwarhj$wHLC?m)8%|UWApURzaU6(=49QoQ!TMwZCiv;>-y(->QBBec2ee^ zo-whtJ@`dyder**9{J4-dk^w&#8dX_3=Gm`bTB-tYGO(gatW!9K)MKnJSDFip`pHmZ6Rv#z)kAQ z4h}xV&dJGEvH)?Z`?)L&uAw^IF0`2U+ZCaa*E^2=@XoDp14I+!FZg3TUh6_*Q+s!J z^H4X2pr?l)b#ii2Qd7J7$63I$N>}v${r%{|@LX_oj^MV4{rdie=D73cIB)3RxUXDE zcKnB}nrGEj1iy3h^6)pF?(Xg%$LH0Rl^aZ5;f==!J&&!xqUBT{vCY&4es@gJ{&S27 zS65VQMZ=!o^OQkdWwCPyzBV@Q3kV2+eK0zr<@X7B;eMmRHz+bv=I~9tJJcfky`hx( z5Fa1^tBpPEBAm1u>(z{*io~|i|C$NS@2&Q)ZJ&-qA5(_KyjxX##J_KfV)+TLs6T`K z7L539mdBKwoP7IM{NK!BBNvyWcyZIjs@qCaq|Ft39$)b-u(VM)kSB=Vv9ALICvim< zGXuBqJxHx$jmI`CvI;sqaa(g6Dmr-UJiX^b=**%-8+e98a&An{#ft{xj!I z!Jbz)xo{I9+j#GIUq z7eD)cpERtdMm{iqVsKxb?L^3onHy=cg->pN!vmSlP z1N}1&`(7*So@!dWmI^s}?7>`bPy2}80_zu1Y&WpT9hU!|k?wjUv1g08-xlZM7lNiX zy<_v*HZ}*)rZV^`cKd;iqiP(cDWmsOr}O)Z&g{>Zrd9Pj4M*)GAJ3gU{O0T6Ov1vF ziJ92|x)k%FINGhE)-D;v02kSz@lU$S6_72ydw${i9t?YR$D_g)_@1>>IRMu+y*z7h z%Wx%sDW?r8k?;-b&i1~(O1N4o2qhWi=H^b5r-(Ybx-q8(H(OyK*6v7Q7s(YyUkxlQ zZIQS^c6wTjptvdf3=+3pp;_!YaK*(PGVN{qRB=P%lGWoLh^rZynC=|TMVosAe1IWU zem2NX z#Z$X~)j&sQGh#(TN=kEaPX`QA20L7-WIW`}}DzK$#5_cE%L|AkWS@q)yy_TI(Vt?M2t~p}cGz3eHZWy?j zr$@fR@zt{U0HdP%`}<>}pW^|irtZReQPR+;K&$V8HkQC@kMQuLx0Is4el5Kf@=j0L z);Ib1_I&jE41D!YXYfbcC?-Aqg3j@EB?}9SC4`YZohb)X?aU)1BP-D|waB~=>0u%;Q~8*!ulFK(a#q5Dk|hz3qt&)kvYd=B_*Y61_tr^6P$2x|D@$+ey(8Y(%$mZhuH1)Dpc4F;2H!mZmqs_bGZ|pRX_!%n#bkdoeLC zR+JW#D{T+3Gw#gF(tq8|`WtU#(7manw>PbD-2{`7qGx5zxGa3*pm6^)s(_Y9l@VF8 zBYge>*KGb?zhH52#W89SZ54l3Qq@eylX8Du3FYI#WC2(uSauK2=sHUmKNCGszTHwy zPwz|#X3Kgpc)~Ps0P88KI4g@iS$f;qkE_e8%h>};*bOTDWT26xqZf^is>a0RK9I`o z+5%W{-uvJHdGFf6_yc}%%^anWC}t?J8-}y2hrMHfD?_SZq{66O(ZMf`T3m2^5&blO z-{3AwOJmhkZ$F~?BUuw3wT6C(gi0lV0b4Toq@V=JN&KDL!2}eOGihC z6H|Ik<94)c`CTm-l4;*S6Pg#?_wa<4)?A^1#Bem6t}q?OB^?~fxeDi_(V1M8)Oc^L zsfmg6u5FO>?@QY;W9vx)H_Oi3JwyR(ZjVS06=L{OMsDs_pGs?+-O&x(Jwdcw*uqNvLrcxw>TIj6#7rMPirbWkRv62FityjR?srT> zRB{wKh9G`_$ehXF!`CP2z8-*(B@~A- z^*x=PUm>X8?=9i?jM;T*)6!w_xcK@Bo%kQcJ0RU$0^(^NS5_&T~Z(F{|CY);X`0*iZC>Z>RK}JRbO%-|cjY&?QH_HwoBukVE=>^AES_$W3mGk()Pqh*aZ0tV%N!oga^jtZ=%! zt=ca?dMKT6*Q0t)Uwq;*#*7~dPYz1(XoX8PW1r$gjM{^Hey1h4Uq_c8lQ$ZBXQezW zd=N*4p*ezGa$LIYL?>TM1J9?7a8{h$$ZSf7tYU3uf6Xofjj**OC5&9Ze53;NonHg> z@Deo<<#2r6^O7<%F^O3f3e-Eq5l9Qyp&v^sfQ~c2tM=oJi~sB)#!g?hvLYC9_3_8i zXVJf#Tb1+*y%qLsY57oLt7dSY>0q6m-Lp<7lS1BC=kl-we*WC)ydhJvN{s${CnM`9 z)8wZP;t^}-;-71S#QSu5XMX)=%Kf7CJ%6#9WW#DUD(SLI(Rm~KU2`klLDo-u1)&8l zLtlN5e$q`R7dE?+^_F~_|pJ>f5>6KDne{x!4hxwn_^|Mz1-!JQb zQt?bW{aJ-i;uCd=BTs1U>(DP99UWf$)$jEV0e`<9^tjY=f8wsvo-I9b{kb_gx))QY zPb&w^07#~EW)&8`Mu7X6JRF<|Ans*u$A9S(^42FXi}%tL!B+#;g^_|3X^d|7ZZ7z( zROQ6?^iak#4xkrKIr0u&!=sBsdrNI@7z;MXT{W<-m2)x^W_^cg1F$|~9~|kLu-mzF zCqc7~DY>>r;CxGi5+8a?(U!cU5*rVrGu|~ci$W3I$*n5I`>^iRR*uxwB|4NB>C_+a zm2ONEotHNTqm?&CHnv_}-uV0T+FGKzLw5LxITz!;-nYm+E*`$?H1%4W5=`TD4mORp zE-d8!x%=VRDfW0npsFoIKx;4lv9O2;g9l^Hky< z6~S8E+IqHOz#^~f4SWFZ$8!}wp4|#VDXFnfo-I}9C6L%O!j zvPIMSOqdcKeG*QwU!I2ki1h#lOfvIs$7kmKkM_Pi9LqIYUnQ9mGNlrtOv#if!&WFN zg%6pF%tJ(FDhZ{}l&Qg3Br_Sl3<)U-Nv3Sdln|24BK+>RbFTCE@6U72bzJYYuf2ET zec$hS*0a`q-|Jqo=_m4bc~|zF6-NW*SEMK8sZkhF5Qk|=-vg`-QieYoOE@~F1{=b) zhUO~y;sod*R3|5Vbtd1zJPvdsX-MQ36l|$lkimzgH{TC~HhLdv$CnsOVvW|2!L!GX zZLuokf|*7U~mdPE1&rhhZPJ_M4z@{-F-#g zScc)P+DTSOchMDSKcp+mh1K(aHnS0$DT$#`iF^0E1Vv zr2Qk2k0+~Ki}!pz>AW!C7TCkK{o6TMEql0$vyqL z!gxCUpgehuS6KXM-WmQa675&=mrI_*wI6FXJ2J^YpbjO`+DRm~zkW&Jed+XOKSH&H zHAz}kjzU)rm20qaXGMM=ktn07%zW0QV(Wy&sdF7q}9a^>@U~4K}NzQ%> zbl{x5{r3(6NI~ZZ*Y4d0*N9Qp>Gb#DEkx8)X6-HUdJALi`#XoQMTI%t?h4SsAL1<< z#o4+VIy((8-GwiG_M7(Olirb*?GZ#cx7^<0x-hGo)p<_h!l9%Uul=#`#L5ae!OQ&4 zarHd*E-Zlzx=vx(My2l}6YzwQV*>;ttZ2^WvyZxXcA2D zL}&}b;F(AR|7|wLZ_&BeZ z;Bg)Rm0|yWyc$o_EjF?V?7MZ~UejI^7s>RIu(}}6B-(m;$)^`2py{W#NN_i@(hzn2 zjQpw4NU8KA(E^AsIhka~B)detOm@n%`Kh($`;lc{-;3DlPkB~clB_jwd?`QZcD9gC ztZMBr#ph^i0<0hNB9QgoTTEr83EY6XzdhGSANwD*V}3(<$-P{Y2YnN!(WiFmT;=oe z@bKs<$h#7gx43cX$a$%UZQ1gi2URZVp;6FqRNh5K+PF31WX90Ggk--WUNt^uYQFSs z$`0n=j!bgvlcja$IyyUp(feqtOz((^+vo-idv1!UQ#i=9RLKr|e_PeEh#%kzZ+td? zaeb{oL+0kIYL2#$+xh;}rvt=ZAAfx3qP%Y<=OGowl!l#H4reMKayF8-9(ZFVsdR0b zG=D1{fm}#)1{k45m0#BKzX&WIeLRQu#n8dCwwA!ZGBTtq_)1OkFu+ixOzKy?%?k@D z{f4!D?Mc^Tw1l2_JCj)&_4*b0#d}*XQYeo!(97*qb)DGW*p>6(4n+T{*PVT;8Jtp8BBPx^Kg8LPcKE#%K z@J>p&Iq-3Bs;$4(6M6eUCg~vK?JA`V>ZC7nC+^w&n4GkkqsftFPmjHYks1E^id)~G zlXwIS;h>B5F+i@|T-ch@BGvhrK%q&DOS^iN6FI}b-Q8N;Rn%%XTJ6<=9-_^dY~^TI zR2@!gykfVvo2|;zQaUCpGFT>DtgEDqW4t#Y{rUBo+>JbTx(sGt{EU)JwzQ(4d^@V2 zaNYb}(wX3s%+v^*7&*GzqF~Yfb2LI!T^HQjQlCDhAp&|U%ZYI$c+8<2*W3M9Y>J2M zs`uT>D_bi3yjbn$k}D&+0v8d{5rvDcuC{DtIu>5PF0Y;xMx=6S)BXn=&3k9waf8Y) zB_`*h1hUR$^bSrOZ=44HQ~T|>z{Z#cPtreW{|YdOU9Yz6l5?OqXx`*~vU)gCYbV3% zl=|s9iI*k^!d{q(|M}hG|6wW7aktW_L$8G*d`sDaZTwU$HH&r7`8>|%@A^DJIiZ;b z!?xiZ^JlL8G+pGK)DYM)e9LorlJxaOju2^3;@82oiT3J|djB)qtOcK-ACsjFa6p@Q zGLfP9ew}%_?>GZkg|n8HM%QwytE&h4YxtvRadu=YhVQqK%zCg9psmOqi`N$dM44$X z$h4>$I0lHuM8#OqGRG!gP&+>1kkXycE^dHy1Rg?k{%O;C%VUO-2A_G3woK6LW+_zn z20jTzCw^VHz5uJ1uWoVAz8adq$C()iv9iO$!v(jRHa9nWcNdH7zFxftxqwcE?N_9O z#;13rz86Tay5i>cXksG!)K1Z0suSH!ojj+$Fo-Bn+EyACI8N?E{qMS#Kl*fBJ^fZd zsEi;vb$sh0hKn{{BqO|BPJ2&UbQV?zQd_;cVA{s|4cVUXeXkGq0|Phx!7y4a-xz=r zt?&k(z0%4_)a0?>bHg2qSyCdrSvPU&o_fEYbOfJrIy}S~qZ8nyRa32`782 zl4Q`f8rJW>t~_{Ffb|ueI@}Oq^iy_*YfjIN>34?u7RG9+IygBo5V<+Gyo>Bk*WQY_ z*(>SRuC9MUKNIi#^z_YeZg}tgLMW=vN;hxdsnH-KMa1)Xaff`IxGtMPfv*YY<5u5- zaw|h!U7$W$XQh=6A7*&bd)?MO4XH|JYaS4wE4j?+0Rf`BDGJ}4-8l?xN>2j2K`XNe z8T}HjO1=w>SeMzpo6vNJ?C%KWD+yrA&lNg{14ybbU7G1=W<_4%hAz09=G6dRr~mw( z)o&8FEOXm{zTI~YFX(lJ*lyyOooFe-q#m>!lXh8}njXbZ87a8$?Bk5mY@|S=uKt3Q zut=dS;4zxgU9{B9c(HQ+vvR*(`Qs#L@;y7`HjGH| zW>&qj{}iVAs8Gh0M0_AHf)dtLUszc9HQS;}>~h3SurDAzz)HL!VjV46Kubf;2lK=O z0T`xKjG`#kS2NmUa`tQu3SXVx(_?Ut%GNj34iIair3oaM66%d~dtRMmetd=&iz({( zx9`40&tPT5Pr&HeH2J~7n+YpuZki#vnEl3L_o92M0;DoH%E;a=x%jgmQ&UIp*-*+rwR^)skx0sa6^Cf1;lhoA&$0Wt_50#Vs8xx~nlC4P@WA2< zNE-YzvM&d06y|ts1adg_v`v!HY;5`9+KyiZ>**?^Yp2&lAE`I)!VGZ$G(!Z|8JZ~8 z1m5P&*RNk6j^Z3c!XW%F%cB^eg&8cVs^B9NSbt-$7@prrIZQ_PzW?;aD~-i_~qXTrfadmskzm@o+aj#X88ZACVH z7H7q{lu2^L8KKxKgOMaGHAjxJ+Cd3`Y6ks*5nC39)(uSe?8fRr!+#o;_aDEEX)h&4 zv(2{xM9uHtzTv;zzbSHkx5VoB356x+5Y~@hX#T9M3xD)5&N1W&0=WL=^)V*oaP2LUu6*m*At1Bw9D?ahw zr+>Uyg{@8?5xXo+Oxm2ckHU)m$zXIlop63umX>ab#yuPO#l`)2w`4qXIh>h#12A8J z{7q$-98jL*TfEHn{!AZSKh)}6&;mjP#fihE{F90^WjnkE&kr=JPYvAPrU(<8tuRZu zgChCK#W)+#j6V%rTroIezQi@mau8sISv}cY0_h0~I>E`Dl-5CN-O!8xX0|b_MDef4{^RM&sk`|z%H!&jlNrCA*CsyG_4Dy8eX6lV-aDKRl z_T;Y>#l>kA8P-6~M*P=d!{q*QJN4jqMdo#Xt!PZui%<#E+>1v6Da(}5Y$(wcEU(7w zhWZk3*UHwG#?;hQ>u3Nkt-XVT+Qo|(`(%qtk&rQ^E-lKeNz1R53urNGu+Rh!ez&v! zn}%V27uZXW=Q=rX3t4m+0fAyKgBgjtR{QWwuG;1|xXOg=usAAmcUjO{-QirXdJJA3D0aM)SGh7|fQN`{R2x7WifbsfSqITTvxEqfpYl#8~ePfNm`F z9AgFCpts!1c>6WjL*>hjBluuK7pCsd5l$YoW=kd;S{x~<@Ip+V^?KR&q`e(X!J+C` z&KUhMc5QRBtu#U9_$7^S`Jd*X-oSKw6VvT9UUEM`#sLszEmx(b^kAy@k;PYumuK70 z>BY1Qd!hvdEB8EXwng|$&H3WjC+Vj9VFx1(CUFeg3_i2KiVDJah2ns+kAYuK`$S}z);L_ zBW3J(n-kyyH!Hp&!Fym>{qT&Vu!r;?Oql z?LHm7!qc1lR5y<1RdNah=4q)|~UxdjDCdBk&G-XI7| zplpj$e@K3KLp(}eH?SMe`TMJ;c>pu9MhR#aHxd$3Qd6U$?y2)eG?-`Ct^ttC1is~v z!lf1cv90YU(39M*{rf}@CTR(tRwlOzIHedCq+dxc{&XT+IoZ{>W6gXlznygWScR$_ z%75GexC9)TgMhb$SD8R^0pZ~^;M>EWDv#Ha>hUx+l>ip0sqpH&lz52iWnY?J-L$ef zw)COiz>Me5IS5)`M!^W9l2HsFQZO>Z6tVmSUpu|>AMndJuKo#k+)tw+Tdo|y$3QG8 zm@R_%z1=l|945Gb>b8K^U7H=Y#XAgh;&wW2*KM^P;OAGY|GE+ucjOhPh)Sx^y)UwS zW1ucTU>r+<9s7CyHqP}TVs!Y-01ltut{Yi~sdCs@i5cxIV?le(NS6|IGORA%!x)uH z%Kk3!fe)Ugg!9tI->2>Z2XL`gX6FHIt)PIx4++n$N6vm zqi*@h-N0jz?>7w}5#u<#{Kcve@Rw473qQYz0HYa63W5j>DShac3&;$i7U?g>ig6-Y zVvT@P1Wr@si!S_%r(v!G#w$XFQ-cLAh&Om?yj1oq6dDCP_=tD zA*o(-mbz?wTC6zIFn<&tWe_=Rc+qPSD|b}A9-1h1{-_}9A~DWyQwtEpz)ib#R3~FQ zjHKu+w=>;lVWim}QQrGAks_&IH|HTAF4jbLTAu!8@D6vvK9yB}RDvZvA|Uh<%7OC^ z4s?*OAjdm)@kx=`(P)7OC-(tB1=aVsu#hG7LG^GjD!Kb|>d@8)K9*SfhS&TIQYe6u zLCI4WzP1PJT(@LmVtQ6l;WJl``Nz-%;Z&SimVVO4$w&jjEWr(CZ8ShTF1j?L7N7)G zTViw|Ep%J4{=x2x#{`|A+yMhd=J_;);ujYo!iudx7J}fO3gJP<lO3QkdEVEJda}olqdvr2P=5Sj?4iP*C=}@2;~8sL%vPUcY+B& z=k6{!c=q(^)4Hel_VK6A1_Xq9f)SUHlng+uC`p0%2`h$2^?F!xG7lK=@wT^SCu{_? z4hH-~bnUAHGQbYReU0m?+E(C`ht{Nv_M@sQf!FU@R<@)uuJ7^vww!Lj*0Q1 zn5Iz~X87~R54Jb)fiX_HnVxl4lD)2XJtPIakxCGPm){eenoXMmsCblpVrLWpDuYzG z{E!Ltgh!cKevmbeC#cps|4`m0$Y}gc{JmcwvBc+fF8}Xh=>N zZoduypuOa$g6qeR9|G6(yHV~Tb;IDE43CKu?a(o`Jp+h!8wQQBC*)5=Q{yih8s5TU zNgD$6fTHWHv9WI0w}zrH%p@XY5KaDcx`Vzjlx<@QaSjg)dsI}Uci#gW;Aha0T@SSv zcL8s_Uz2`iQuHYkvoeD-+lgtWR6QJB@jlaEgU1sd3Xw77MGC)xHNwciV1s3sMbPW> zpsTEWeNCtT)YWMq$r1SSZDRQLp6l;?mizJkEh@bp8z)Gyhieuh;Y1*VIn<_cxPwl0YW)bZ)ax*S(%PbT9f)?(q0ln0hJrTtmfKJ z?f@72{bsjb|2@oE*L?mlbv}eny~pTm?d&cqt5N}ANCl*{*d+|2wo!-X8ozeLL}|#S z42hrgSpu287qp{;ps{(%{tr}pSm$h`p#?jTX2zE+DxLX!8|dG6lQ*@%04plEksj04 zOA9|ry$yE0_t>pVEM*@KQ`|21DAwuS4c++-vZ=&Kdt~*YbZfcUl%VlQ4ta$NF-{l-K5b1|6m#UMv zytb&v-?cGzk2yfQ5pP^d#SV!gm?_qXbW``A5Lh0xtRh`I87=UK7@1yLz?FGwFtpBU zX=!>dj_1rKr=Pgf?3;rV;k%+ZE*Kjo>7Auz7e2Lb`^kOnS(a(CH!l>PB|p7n!OL6& zOc%EkDO(h2x&D`FmEaD~%*+JFxCwbwIg27ok`u}$I!dxlOharZ8yBT@e@e^!>EC)7 z3Rj#Q|HNj8CFny0iyReOsfRk|oHoVXyr|Vk6QI^`G(Zf~excdqOJgHFYIxF~!z~lx z$4#ML$*vPlZ52W+0$VcMmam7|ClC@`8^0`HbrER)Ei63T9BL#F4{2l*MciW+78W`_ zFEMFORr)(C%P_+xPKP6e_F-achmX34WOBq?XGN3qVZx}IK>X6t_fWPUsDNvr9+oQ#ZHIE{lXXp%8&hy~-f z`3P-qRYiny%tM{Fd zxwqJ+XA&KxoIn#K$K&s%Wz>5 zw+uHQ?M9pvh<3OT2En)exxW6;=GkthEkvpQxjOR0eMC?s$WJ1te*EC&VstcF8Z2Sr zqGW~J>6#8bXz;$o#}P^hHietTnW+amCV(|z*ij9v@pa4#G}TFtTxG|%rwWNY%}4Bn z_|eXcMJJE*aBpxeG`*GGTTl?+z!3!qWZ`|nu>%+XpbW$RtC@NIj0D^C>EgDCE__Wd6QR$1_lP~MM`?Aoxp19WW=QPse2Lv zBwJ_MgjB}g*u_%xkS8)&6d1WHS+2-C6w3Y+Ne&qkQ->%@9c3XE!xE zo&C6|9%0OOfyGO`-BF40@wJFk?XLPo9}-Sx4`O-IV|jt@IHt?;5h(;z0l@K2|5j2- zIZY{g)NrBg+Pc7@o4kL^$KMlg9Tc?fWVj{eD;^o_Pox6}qe(o-2rjI5&t; zR$l)5%gL@MUkKZ|T{&4MaPr>C3%ix+-()W-F^n?Z8?FwNV? z1jk0@Eg|v}5M}-BOnVt_5Y{d@?}Zkg!fX1jjY3}QlKb=|Q|sfvD(=q3 zd?lPw-@gkXYx`hkR9ICN0fMnJVVxME*f%npi!<+W**^lT0iP#t;euXJWPVq;CSQ{j zMCAZ#aJAY8HQNR@+aATHxOj7hsFi@5v~*8^${41|zX5%2kntRfCSU_rz_HJFKmq{V zeGroijW6E2ZZgmq)I;0Vpj`kBACioZKMqMhe(t^;%)r?uZrR`6eFrFWUCY$3Uw3zQ zZ*MIpPB)MdwSZw#jpiWZgO=t9764-5fTv=l$X8&@I9bj?t375C0OQi!g}Kz?|18d3 zdiJI7ws0 zBc>%AhLWY_E=ObxPwfoR5 zEAk9H{VFXA95am}2OSyN2h8eTS1X+Q;^qp0=cGmX1-^0{F**K{zW)uhk2w1F?=m$u z*77w6W}7WH=EKH{nnWE(13|p_t*pVpMX2SMY{@c+7 z^JalwIe5yRgHG8nhn=7^n&t(&X*sE!U0il=76-}%0o!1Q$Li8hWCh0usEkP@VKY-4mS%Ph>MG>W_(-(G9{qCF@3zpql3oB z9ZM*b$N2O{Cr@c2zjbxi>pmGae{G56%0lhcWLC%k&-F#}PWA+gemS;*jLy_3n28iHL5M18aVa50K8fI&uVd%af{ z4&ZQtX})I(hf(v-pW@Kyr_-8JmF+7%t`+TB;;MIw@8#9)?5tQ4NucYG9jk9WrRSG- zgqIm2DF`R+3=h7Sd+8GuI{CJm4O`@b%((Wl&)OdaR4>sg;I4Pv({GWZ$7Gsj7>(K-y$|!1+(heD}sAzH~RSc0)$HQf?$*r7N*;sWp|=l+r_ly ztwm>6;f>L3L^X@00u*Q-Pgf=mn#d@R)@x-xpMPvNwTt55wS9A1~ zNN~yIeKfGNELe$W$}8mOuY+|b^kio9l3LM(Jh!Cl&l}NHSmtuLmL47~gvs6ws-qxu+RN)YK-*M`3UfQkbLcu|<9 zwdN@0^CX1Y;@~8R^9T4l{&pg#lY+ebnIF1mjf}SFvTgsQ1*Ip>&rzG0#S4(Fpm7F` zB?+B0W55J~-iv2yhpDR*ajk-^nZ~tyck}ou#hut^pkPi^?gb7&ob%K{@03;d)7v<)1+ zVtiww6PPBlHM9*#<;b;cpk9~OR;C!nP1+#iN9OAXy)X{M?1`f2!fxm9latL}+Ya)l zTGbPGr_S%)`_Ti>K3QV`X|1X642pGg(TC~$JZC)ea&kn6qQX+HFKZR5O+xN>$njx? zQGo?}FRU-R3Y-jGbReRr_6K%4CPg1su(CYd18qp^*;@iy$JD+!%kKA_L2&{$hS=Am zF3RBoS}10Z`TMW8-kOJ0^gq}y$SFbfqAmcAg~vw&)6kq6zv3bw9{Lc_HeV7i_)4#g zs;(JeO;S zu!8IXu>IRM#u2K~+^3HpsX&kyboqJ`a>;AUY(!1jaG`N&o`QZnwqR9c zx0kz*V;dblZ&DzjmEpSO?lx3{=;{Hd>vYW7=!t7dLSK zwZoUjuKc_p7%^{XZXS*}Q&}n7>YZjY{C;@YCO1D{B*L=NOPA# zhZ$1zRwjL)K5_i*Li>Ru6y$7t^{2VH68_BR6w0t)tSCrQWGJ{1T+of-Xhx|?k#_R+ zeIc~b$gjd*Nyz7=1vGe2(+^9dCy*x+jVo_k zxaeCLmvN8{=UY6DFGa7<oat@>StLSL*PF8 z`hf(}GK3=nro3EmUw1M8>@5un8etu+JO4>#u47#ko)sIS6#OTrsm-&-bb1H5Dm_DgFRQv&B`#m=F?;+8H5t%4~&c+ zc;nD;legxaL=D&x=;#n6a9w%v@_U9aIrZ!%_9J)bwcIWFGKB~gZ~v)}iRel}%jU>v zZ(rYAq;-c>?eZC}yD9QHq{GcUqy2lbs(*6p6tt)(xt}xAY$C)4Gv)9p2N$`u(q~@e zymn@*{Ts|BYjaIVMq)nwqr2EoZf_O>Zyk^Q^r;CA-5>;mJp>C?J-rQjdC2E(zuth7 zyVU>KbG#BX@EIHL0C10^1{d;T2!@O=r5v{d{44kqy*Zn^O;5%`b9c8LOki}XqJ;>) z8zWHId|koUU|1*`fSz8iLWd4ih*T}f%E*oad1=KGqc$ju2#vI1t^~it*MDtia5}A4 z^U{)QxYpm*zn$GbIW>$K;4E00Ce`y zM1+%U6p2Vroc4ZymRN|^g>Gl=(g*bB_}5lWZTA5^!-$yP>cys1vvUKf zl|n$p8zxX3efxG7BLrZDbikjdMxoua;q7Mgbg@1)CW( z-oJlu?^9*A4AOOWrUDTk=W9!o)ApzZsG2?$jFQZdxvVYKt6KT{??J4TO$c9cYb}2b z%0MA)Ud2#-^kJ+0Nud;1*@W31p44{efJa`?se2ROpiT-R!ik?;Ku8FIzf}}>JA3z^ zu5+Z=&imoDlwLmFeeeAtFe}s><0<=h{s`DFVqutb=%WAYRH34pjV$a3)JArJ4vK~{ z?EaZ@$jDQXIv2QzcmfHaOA{~zLDq C01 적용 경계에서 한 가지 구현 의존을 확인했습니다. 현재 일반 durable writer는 LocalDataOperationGate에 참여하지 않아 gate 내부에서 revision을 읽은 뒤 await 알림 취소 동안 편집이 들어올 수 있습니다. C01에서 generation/revision token과 단일 claim을 추가하고, DB transaction 시작 시 revision을 다시 검사해 durable 교체를 막는 것은 가능합니다. 다만 stale 후에도 이미 실행한 알림 취소를 없던 일로 만들 수 없으므로 A09의 runtime/replacement 순서까지 완전 충족이라고 주장하면 안 됩니다. 권고: C01은 claim 전검사 + DB transaction 안 최종 revision 검사/부분상태를 구현하고, 검증된 staging 및 모든 일반 writer와 replacement fencing은 A09 통합 미완료로 명시합니다. 원본 데이터 stale overwrite는 막고 OS 정리가 이미 발생한 경우 후속 reconcile 필요 사실을 결과에 보존하겠습니다. 이 경계로 진행하겠습니다(새 제품정책이 아니라 기존 AC의 미충족 부분 명시). + +## Root 답변 + +> 이 경계로 진행하되 완전한 C01 stale-before-side-effects AC는 미완료로 명시하세요. 최소한 claim 이전 generation/revision 부적합이면 OS/runtime/data 변경0이어야 하고, claim 이후 일반 writer가 들어온 경우 DB transaction 안 최종 revision 재검사로 원본overwrite 방지. 이미 수행한 OS취소/세대변경은 typed receipt에 남기고 현재 데이터의 명시/기존 reconcile 복구로 이어야 하며 단순 stale=no-op이라고 보고하면 안 됩니다. DB tx 안 nativeawait 금지 유지. 결정적 barrier로 preview→claim 사이 변경과 OS취소대기중일반편집→최종tx rejection 두경로 실제Drift 검증. A09에 모든writer fencing/staging/runtime순서 통합을 명시해 C01닫지않음. 이 실제 구현 의존 질문/권고/답변 원문을 /tmp/c01-review-transcript.md에 보존해주세요. + +## Root retry/receipt 검토 + +> root WIP 리뷰: BackupWorkflow._retry 단일 Future가 receipt/generation 구별 없이 공유되면 이전 restore의 느린 retry 중 새 generation의 다른 receipt 재시도가 이전 결과를 받을 수 있습니다. retry는 operation identity+generation 기준 singleflight로 묶고 과거 receipt가 새 작업의 후속으로 잘못 합류하지 않게 해주세요. 같은 token의 동시 apply 합류와 commit 후 재제출(재적용 금지)의 typed 결과도 별도 테스트. _completed Expando의 과거 pending receipt를 retry 성공 후 다시 읽을 때 pending이 계속 남는지도 확인 필요합니다. 아직 WIP임은 알고 있으며 final 전에 위 경계 확인 부탁합니다. + +> 또한 _restore의 await _backup.apply(input) 뒤 generation getter를 읽어 committedGeneration으로 쓰면, commit 이후 반환 직전에 다른 replacement/reset이 generation을 바꾼 경우 이전 commit을 새 세대 receipt로 잘못 귀속할 수 있습니다. adapter apply가 실제 claim/commit generation을 반환하는 immutable receipt를 사용하고 후속 currentness는 그 receipt 기준으로 검사하는 편이 안전합니다. 에러 catch에서도 '현재 generation'을 이전 부분작업의 generation처럼 기록하지 않도록 시작/claim identity 구별. deterministic after-commit/before-workflow-continuation gate change 회귀 부탁합니다. + +## 구현 담당 답변 + +> 리뷰 2개 반영 중입니다. retry singleflight는 receipt 객체별 Expando로 바꿨고, commit 직후 아직 delivery 대기 중인 동일 token은 completed보다 pending Future를 우선 공유합니다. BackupOperationsPort.apply는 claim이 발급한 정확한 generation을 반환하도록 바꾸며 catch 부분 결과에도 claim generation을 보존합니다. retry 완료 시 약한 참조로 연결된 token의 cached receipt도 갱신해 오래된 pending을 반복 반환하지 않겠습니다. 초기 회귀에서는 비밀번호 dialog 뒤 busy spinner 때문에 pumpAndSettle이 끝나지 않는 UI 상태를 발견해 입력대기와 실제 IO 표시를 분리했습니다. + +## Root UI currentness 검토 + +> 추가 UI currentness 리뷰: _loadFreshness 성공에는 generation 검사지만 catch에는 없고, _export/_restore catch도 _routeCurrent만 검사하므로 같은 route에서 세대 변경 후 늦은 old error가 새 화면에 표시될 수 있습니다. 실패 경로에도 operation 시작/claim receipt의 currentness를 적용해주세요. _resetData confirmation 대기에는 _busy가 false이고 확인 뒤 mounted만 확인하므로 확인화면 중복/대상세대변경 경계도 확인. reset은 성공적 invalidate 자체를 stale로 오인하면 안 되니 destructive acceptance 전 확인토큰과 결과표시소유권을 구별해주세요. 기존root검토와함께 transcript 보존 부탁합니다. + +## 구현 담당 반영 + +freshness/export/preview 실패 경로에 시작 generation 검사를 적용했다. reset은 확인 화면을 열기 전 busy를 획득하고, 확인 직후 route 및 기존 generation을 검사한 다음 명시 reset을 접수한다. 실제 reset 결과는 자체 invalidate를 실패로 오인하지 않는다. old error/freshness/disposed route 회귀를 추가했다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 2c47089c..e9fb33df 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -460,18 +460,31 @@ "저장/복원/초기화를 domain workflow와 data transaction으로 묶고 UI는 결과만 받도록 한다.", "MyDataScreen이 BackupService·ResetService·Reconcile을 직접 조합. A08/A09/D02처럼 DB·runtime·OS effect의 경계가 여러 계층에 흩어져 있음. 정상/부분 실패를 하나의 계약으로 테스트." ], - "status": "ready", + "status": "partially_implemented_integration_pending", "agent": "/root/grill_c01", "issue_number": 600, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/600", "decisions_file": "plans/audit-2026-09-23/issues/C01.md", - "implementation_commit": null, + "implementation_commit": "4529d9f84e911b1ee2fbb42af09698a0c27cc9f2", "pr_url": null, - "verification": [], - "blockers": [], + "verification": [ + "Full Flutter 959 passed; app-owned coverage88.74% (13507/15221); analyzer clean", + "Actual stale-preview baseline failure and Drift race/rollback/receipt regressions verified", + "KO/EN 200% eight widget captures reviewed; source24/commit hashes match", + "Generated/local-only/manifest and Python11 passed" + ], + "blockers": [ + "A08 schedule aggregate FormSubmission integration is pending; no atomic aggregate completion claim.", + "A09 staging Local Data Store, runtime/session replacement and all-writer fencing are pending. Candidate remains authenticated parsed memory data, not verified staging.", + "Revision is checked before OS cleanup and again inside DB transaction; a racing edit during cleanup prevents DB overwrite but may leave OS cleanup pending. Receipt exposes this and retry reconciles current data only.", + "D02 damaged-database restore integration is pending.", + "No actual Android/iOS picker, encrypted file roundtrip, provider delivery or reset device proof in this change.", + "Remote CI pending" + ], "sequence": 14, "readback_verified": true, - "body_sha256": "2fb0876dfbbef2ee14038ab85bf1fb4e9bb724197551a76889309c93a19d8c1b" + "body_sha256": "2fb0876dfbbef2ee14038ab85bf1fb4e9bb724197551a76889309c93a19d8c1b", + "issue_body_sha256": "e882ebf132325c47df8890eb51f5514af5fb23020e264bdddcc11a4bb9374650" }, { "audit_id": "A08", From 60b2c36248d445769b996c214957a4464702b309 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 11:59:09 +0900 Subject: [PATCH 48/59] docs: define R01 protection policy and A08 persistence decisions --- handoff/audit-2026-09-23-progress.md | 2 + plans/audit-2026-09-23/README.md | 6 +- .../a08-implementation-preflight.md | 28 +++ plans/audit-2026-09-23/issues/R01.md | 158 +++++++++++++ .../r01-baseline-readback.json | 223 ++++++++++++++++++ plans/audit-2026-09-23/state.json | 12 +- 6 files changed, 420 insertions(+), 9 deletions(-) create mode 100644 plans/audit-2026-09-23/a08-implementation-preflight.md create mode 100644 plans/audit-2026-09-23/issues/R01.md create mode 100644 plans/audit-2026-09-23/r01-baseline-readback.json diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 093e0263..5d1b1967 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -126,3 +126,5 @@ A07원격최종28baf6c7 양CI성공: Flutter35947757749=944/analyze88.70%; Andro ### C01 구현 전달 및 다음 A08 C01 source4529d9f8, 전체959/88.74%, 소스24/commit 해시 일치, KOEN200%8장 root 확인. 실제 음성 회귀와 추가 문답은 c01-final-validation.json/artifacts/c01에 보존. #600 OPEN: A08 aggregate, A09 staging/runtime/writer fencing, D02 손상 복원 및 실기기 미완료. A08 전담 /root/implement_a08에 Flutter 독점권 전달. R04#618 게시·readback 완료,32/64. R01 전담 /root/grill_r01 문답 진행. PR585 본문 승인 질문은 여전히 미응답이며 동결 payload를 변경/재게시하지 않음. + +R01 상세 이슈 게시·readback 완료,33/64(미생성31). 기존 rule2100824 disabled/effective없음 fresh read만 수행, 원격 정책 변경 없음. R09 전담 /root/grill_r09 질문 진행. C01 원격 head aa2fda68의 Flutter35949254397/Android35949254443 초기 in_progress 확인. A08 Q1~Q3 schema3/store+aggregate incarnation/생성 전용 revision fence 결정은 a08-implementation-preflight.md와 담당 transcript에 보존. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 2dd61a3d..4a442ef3 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -45,14 +45,12 @@ | T03 | [#613](https://github.com/DevKor-github/OnTime-front/issues/613) | 실제3문답·실패경계22/암호문12행렬·기존증거 재사용/실제저장소·새프로세스 검증 계약 | | T04 | [#614](https://github.com/DevKor-github/OnTime-front/issues/614) | 실제3문답·실제 진입점/독립 oracle·Auth 복구/삭제/세션 연결30개 검증 계약 | | T01 | [#615](https://github.com/DevKor-github/OnTime-front/issues/615) | 실제3문답·정상main/암호화저장소·OS수신/프로세스재실행·양플랫폼18개 E2E 행렬 | - | T02 | [#616](https://github.com/DevKor-github/OnTime-front/issues/616) | 실제3문답·native15/플랫폼물리20행렬·OS경계/기기격리·실행증거 계약 | - | R02 | [#617](https://github.com/DevKor-github/OnTime-front/issues/617) | 실제3문답·동일 PR 소스 양모바일 build/native/smoke·최소권한/실패판정·12행 CI 검증 | - | R04 | [#618](https://github.com/DevKor-github/OnTime-front/issues/618) | 동일 품질 재사용·검증 AAB와 전송 바이트 연결, 16개 검증 행렬과 실제 3문답 확정 | +| R01 | [#619](https://github.com/DevKor-github/OnTime-front/issues/619) | 실제 3문답·비활성 ruleset 재사용/최신 소스 검사·독립 리뷰·15개 검증 행렬 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **32/64개**를 게시하고 원격 본문 동일성을 확인했으며 **32개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **33/64개**를 게시하고 원격 본문 동일성을 확인했으며 **31개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/a08-implementation-preflight.md b/plans/audit-2026-09-23/a08-implementation-preflight.md new file mode 100644 index 00000000..9f7fe84c --- /dev/null +++ b/plans/audit-2026-09-23/a08-implementation-preflight.md @@ -0,0 +1,28 @@ +# A08 implementation preflight + +## Verified starting point + +- Baseline source C01 commit `4529d9f84e911b1ee2fbb42af09698a0c27cc9f2`; delivery head `aa2fda68fbf3ac431738de3792f5d587d35b6dda`. +- C01 local959 tests, app-owned88.74%, analyzer and policy gates passed. Remote Flutter35949254397 / Android35949254443 in progress at initial read-back. +- C01 does not yet connect schedule FormSubmission, verified staging/runtime replacement or damaged DB restore. #600 remains open. +- Dedicated implementation agent `/root/implement_a08` owns product/test/generator execution. Root owns issues/state/shared audit documents and GitHub writes. + +## Confirmed implementation decisions + +1. Reuse actual existing recurring transactions. General schedule/place/preparation/revision commits atomically, protected fields/ownership preserved, read snapshots consistent. Connect C01 typed durable/delivery results to actual FormSubmission/UI. +2. Schema3 additive aggregate incarnation/version/last intent+input digest, local aggregate records rather than global ledger. Actual v1→3 and v2→3 migration and backup1/2 compatibility are acceptance criteria. Internal receipts are not exported in backup2. +3. Recurring series root segment owns create/following receipt, per-occurrence Schedule plus root version changes atomically for occurrence edits. Pure new materialization does not increment user mutation version. Existing occurrence changes affect stale baselines. +4. New-create absent-target baseline uses durable revision fence; a concurrent unrelated durable edit can require explicit review/new intent while preserving the draft. Edit uses aggregate version, not global revision. No silent baseline refresh. +5. Root verified current restore uses backup revision+1, so monotonic installation revision was a false assumption and explicitly withdrawn. A new persisted store incarnation on Local Profile is stable across process restart/ordinary profile writes and reissued at actual restore/reset creation. It is excluded from backup. Draft/receipt compare store plus aggregate incarnation. Future A09/D02 profile-preserving replacement must reissue it. + +## Validation and evidence + +- Reproduce current partial-write failure with actual Drift, preserve negative proof, then use deterministic rollback/barriers/reader snapshots and UI result assertions. +- Same-intent pending/replay, commit-receipt loss+new instance, create/delete/old retry, split/removal, ABA, old store with same revision, unrelated edits, partial delivery retry all require actual tests. +- Native OS awaits never run inside DB transaction. Strict local-only and ignored generated policy remain. +- Implementation agent preserves full actual questions/answers/corrections in `/tmp/a08-review-transcript.md`; root appends them to #601 with source and exact verification receipts. +- Source freeze/hash before final suite, whole tests/coverage, analyzer and appropriate gates. Root independently reviews and verifies committed blobs. Missing physical/device/remaining dependency evidence stays pending; no false issue close. + +## Publication limitation + +PR585 body edit was rejected by auto-review for lack of explicit authorization for that body/destination. Approval question remains unanswered. Frozen approval payload is `/tmp/ontime-audit-pr.md` (SHA256 `107246ced5bc0a88cff9f191ccdefcd5d89373c873fb28e92af46402d45d9d41`). Do not retry or publish via another path. Detailed issues and implementation continue under user authorization. diff --git a/plans/audit-2026-09-23/issues/R01.md b/plans/audit-2026-09-23/issues/R01.md new file mode 100644 index 00000000..cf5f5df5 --- /dev/null +++ b/plans/audit-2026-09-23/issues/R01.md @@ -0,0 +1,158 @@ +# [P1][R01] main·release·hotfix의 실제 보호 규칙을 활성화하고 최신 소스의 필수 검사·독립 리뷰를 강제 + +## 문제와 결과 목표 + +CI가 성공했는지 사람이 확인하는 절차만으로는 실패하거나 오래된 검사 결과를 가진 변경의 병합을 막을 수 없다. **2026-09-24 02:50 UTC의 fresh GitHub read-back에서 main은 protected=false, active effective rules=[]이며 legacy protection API는 Branch not protected(404)**다. 다만 ruleset 자체가 없는 것은 아니다. 기존 review ruleset ID **2100824**가 **disabled** 상태로 남아 있다. + +R01은 이 기존 ruleset을 재사용해 main과 release/hotfix 대상에 **PR·최신 소스의 실제 필수 검사·독립된 적격 리뷰를 강제**한다. 보호 설정을 문서에 적거나 CI가 green인 것만으로 완료하지 않고 원격 active/effective 규칙과 실제 실패 차단의 증거를 구별해 남긴다. + +- 우선순위 **P1**. 전담 agent `/root/grill_r01`, 적용 skill `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +- 고정 코드 기준 **28baf6c7b91c00d2c6490f597375c1dad39fc2b6**. 병행 C01 WIP는 읽기 근거나 실행 결과에 포함하지 않았다. +- 이 문서 작성에서 GitHub GET 조회·공식 문서 조사·이 파일 작성만 수행했다. 원격 설정, 브랜치, PR, checks/statuses를 변경하거나 Flutter/CI를 실행하지 않았다. +- root가 사용자 대신 한 번에 한 질문씩 실제로 답한 문답과 후속 보충을 아래에 모두 보존한다. 새 제품 도메인 용어나 어려운 비가역 아키텍처 결정을 만들지 않았으므로 CONTEXT/ADR는 수정하지 않는다. +- **현재 상태: 설계 및 원격 baseline 조사 완료. 보호 적용·실패 차단 검증 미실행.** 기존 CI 성공을 R01의 새 완료 기준으로 소급 계산하지 않는다. + +## 원격·고정 코드 증거 + +원격 조사 시각은 `2026-09-24T02:50:25.044233+00:00`부터 이어진 같은 조사 세션이다. 원본 응답은 로컬 `/tmp/r01-github-readback.json`, SHA256 `a3b6981fef9973d12cc3d1924df7677e63457c7f7feb1e8dff55dad4004b0525`에 보관했다. 이 임시 경로가 GitHub 이슈 독자가 다운로드할 수 있는 영구 artifact라고 주장하지 않는다. 구현 증거는 비밀 없는 repo-local 산출물로 별도 보존해야 한다. + +| 근거 | 실제 관측 | 의미와 한계 | +|---|---|---| +| `GET /repos/DevKor-github/OnTime-front` | public, default_branch main, 현재 호출자의 admin=true | 기능 가용성과 현재 API 권한. 적격 reviewer 존재 또는 설정 변경에 대한 운영 동의가 아님. | +| `GET .../branches/main` | main SHA `c7184b2ae477e6a24a47fd0b9da79d3ed52b8cf4`, protected=false, protection.enabled=false, status enforcement off | 조회 시점의 main이며 고정 작업 source28baf와 다름. | +| `GET .../rules/branches/main` | HTTP200, `[]` | repository/organization 등에서 적용되는 active rule이 조회되지 않음. disabled 규칙 부재라는 뜻은 아님. | +| `GET .../branches/main/protection` | HTTP404, message `Branch not protected` | 성공한 repository/main/rules 조회와 함께 legacy 보호 부재로 해석. 일반403/404를 항상 보호 부재로 판정하는 로직은 금지. | +| `GET .../rulesets?includes_parents=true` | ID2100824, `require review before merge main`, Repository source, disabled | 기존 규칙을 중복 생성하기보다 보존·재사용할 근거. | +| [ruleset2100824 상세](https://github.com/DevKor-github/OnTime-front/rules/2100824) | `~DEFAULT_BRANCH`, exclude[], deletion/non_fast_forward, PR approval1, dismiss_stale_reviews_on_push=true, require_last_push_approval=false, conversation resolution=true, bypass_actors[], current_user_can_bypass=never | enabled로 읽지 않는다. 기존 allowed merge methods merge/squash/rebase와 새 범위 밖 설정은 보존한다. | +| 전체 페이지 branch 목록 | main, release/1.0.0, release/1.0.2-build-54, release/1.0.2-build-55, hotfix/fix-calendarscreen-merge-conflict, hotfix/remove-full-screen-intent, hotfix/schedule-create-form-next-button-enable 모두 protected=false | 첫100개에서 main이 보이지 않는 것을 부재로 오판하지 않고 pagination 후 확인했다. 다른 모든 branch의 효과까지 증명하지 않음. | +| 가상 이름 `release/r01-probe`, `hotfix/r01-probe` effective GET | 각각 HTTP200 `[]` | 규칙 조회만 수행했으며 branch를 만들지 않았다. 실제 branch에 write를 수행한 증거가 아님. | +| exact28baf `check-runs` | `test` success, `release-manifest` success, `build_and_preview` skipped. 모두 App id15368/slug github-actions | 실제 현재 check 이름. UI workflow title과 job context를 혼동하지 않는다. skipped Widgetbook은 모바일 품질 필수 검사로 추가하지 않음. | +| [Flutter Testing](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/.github/workflows/flutter_test.yml) | main/release/**/hotfix/** PR·push/dispatch, Flutter3.44.4, generator/policy, analyze/full tests/coverage80, job test | required policy와 최신 SHA 실행을 원격에서 강제하지 않는 현재 간격. R02/R04가 최종 정의를 재사용한다. | +| [Android Notification Contract](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/.github/workflows/android-notification-contract.yml) | release APK·packaged/merged manifest·digest, job release-manifest, 일회용 검증 서명 | 이미 PR에서 존재하므로 감사의 Android build 부재 문장을 반복하지 않는다. 실제 OS 수신 또는 배포 artifact 검증은 아님. | +| [R02 #617](https://github.com/DevKor-github/OnTime-front/issues/617), [R04 #618](https://github.com/DevKor-github/OnTime-front/issues/618) | 양 모바일 build/native/core smoke 및 공통 품질/정확 AAB 연결의 설계 | 미구현 check 이름이나 미래 통과를 현재 required list에 가짜로 채우지 않음. | + +고정 workflow와 현재 파일은 byte 비교로 동일함을 확인했다. Flutter SHA256 `ef64ebed91dcc4e8f38040b0346f9a5d3645ed129dec67132f67659599f4f4ad`, Android contract SHA256 `e2e36fc2445d2c08070f70fbbdfd68f3cc8a1c999d721f7b0179e5d70288ec27`이다. 이 확인은 workflow 실행이 아니다. + +## 공식 문서로 확인한 경계 + +- [GitHub branch rules REST API](https://docs.github.com/en/rest/repos/rules#get-rules-for-a-branch)는 active 규칙을 상위 수준까지 포함해 반환하며 disabled/evaluate 규칙은 제외한다. 존재하지 않는 branch 이름도 효과 조회가 가능하다. 그래서 규칙 목록, 상세, effective 결과를 각각 읽는다. +- [GitHub available rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets)는 PR review·예상 GitHub App·strict 최신 base·force/delete 규칙을 설명한다. settings 저장 성공과 개별 PR 차단은 별도 확인한다. +- [GitHub required-check 문제 해결](https://docs.github.com/en/pull-requests/how-tos/merge-and-close-pull-requests/troubleshooting-required-status-checks)은 latest SHA, head/test-merge 구분, 최근7일 성공, skipped/neutral 허용, 조건 skip과 실패 dependency의 위험, dispatch-only check의 PR requirement 불충족, merge_group 필요를 명시한다. 이슈는 **GitHub 기본 성공 판정보다 강한 실제 suite 실행 gate**를 R02/R04에 요구한다. +- [GitHub ruleset 생성](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository)은 fnmatch에서 `/` 경계, active 즉시효과, bypass를 설명한다. 단순 `*`나 검증하지 않은 `**` 문자열만 보고 모든 중첩 branch가 보호된다고 말하지 않는다. + +위 공식 근거는 정책 설계를 위한 읽기 결과다. 실제 repository에서 보호를 적용·시험했다는 증거가 아니다. strict local-only/Android·iOS 제품 범위는 기존 [ADR0016](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/docs/adr/0016-support-local-only-on-android-and-ios.md)·[ADR0024](https://github.com/DevKor-github/OnTime-front/blob/28baf6c7b91c00d2c6490f597375c1dad39fc2b6/docs/adr/0024-enforce-no-network-in-product-builds.md)를 유지한다. GitHub 검증 도구의 network 사용을 앱 runtime network 허용으로 확대하지 않는다. + +## 확정 정책 + +### 보호 대상과 review + +1. 기존 repository ruleset **2100824**를 백업·fresh drift 확인 후 재사용한다. target은 branch, enforcement는 최종 active다. main을 명시하고 `refs/heads/release/**`, `refs/heads/hotfix/**` 계열을 포함한다. default branch alias를 유지할지 정확 main도 포함할지 최소 payload에서 명시하며 main 보호 누락을 허용하지 않는다. +2. 단일 이름(`release/v1`, `hotfix/fix`)과 중첩 이름(`release/team/v1`, `hotfix/team/fix`)을 각각 effective API로 확인한다. 필요하면 `refs/heads/release/**/*`, `refs/heads/hotfix/**/*`를 함께 사용한다. 대상 외 `feature/example`, 유사 접두사 `release-not-a-target/example`은 실수로 잠그지 않는다. matcher fixture, 가상 이름 effective 조회, 실제 branch 효과를 구별한다. +3. PR을 필수로 하고 **작성자 및 최종 pusher와 다른 적격 reviewer 1명**의 최신 변경에 대한 승인을 요구한다. stale approval 해제, last-push approval, unresolved conversation 차단을 설정한다. agent 자체 검토는 GitHub 적격 review를 대신하지 않는다. +4. force push와 branch deletion을 막고 필수 검사는 strict latest-base로 강제한다. 정상 PR 병합까지 막는 `Restrict updates`나 새 branch 생성 차단을 의미 없이 추가하지 않는다. +5. 상시 admin/role/app bypass 목록은 두지 않는다. 혼자 작업해서 approval을 구하지 못해도 self-approval, 위조 review, 자동 bypass/보호 해제로 해결하지 않는다. admin API 권한 자체는 동의나 reviewer 가용성을 증명하지 않는다. +6. CODEOWNERS, signed commits, linear history, merge queue를 이번에 새로 강제하지 않는다. 기존 허용 merge 방식과 관련 없는 정책은 보존한다. queue는 R02/R04 merge_group 실제 지원·실행 증거와 별도 운영 선택 이후에만 적용한다. + +### 필수 check와 실제 실행 판정 + +| 품질 범위 | 현재 시작점 | 최종 requirement | +|---|---|---| +| Flutter quality | 실제 context `test`, github-actions App15368 | R04 공통 품질의 generator/정책/analyze/full unit-widget/coverage80·필수 결과를 검증하는 안정적인 실제 context | +| Android release manifest | 실제 context `release-manifest`, 같은 App15368 | 실제 release APK·packaged/merged manifest·identity 결과를 검증하는 context 유지 또는 검증된 후속 context | +| Android·iOS build/native/core smoke | 현재 미제공 | R02의 실제 필수 child/result를 검사하는 mobile aggregate context. T01/T02 미제공 행이 있으면 완료 아님. | +| Widgetbook·Play deploy·수동 dispatch-only | 보호 requirement에서 제외 | catalog 배포나 실제 store 작업을 모든 PR의 조건으로 만들지 않음. 별도 R03/R04/R05/R07/R08 경계 유지. | + +- 최종 context 문자열은 상상한 이름이나 workflow title로 정하지 않는다. 실제 `pull_request` run의 check-runs, event, App ID, latest head/base/test-merge SHA에서 읽는다. 최근7일 성공 조건과 중복 job/status context를 확인한다. 동일 이름의 status/check가 혼재하면 모호성을 없앤다. +- App source를 실제 github-actions **15368**로 묶는다. 다른 integration/manual status로 같은 이름만 올려 requirement를 만족시키지 못하는지 확인한다. Actions App 바인딩은 동일 App이 발행한 다른 workflow의 변조·의미 없는 green까지 방어하지 않는다. workflow/gate 변경도 독립 리뷰 대상이며 이 보안 한계를 숨기지 않는다. +- required 설정만으로 skipped/neutral을 막을 수 없다. R02/R04 aggregate는 `always()`로 실행하고 필수 child **success**, expected case 수·목록, 필수 report, 같은 source/checkout identity를 확인해야 한다. skipped/neutral/cancelled/missing/0 tests/dependency pending은 gate failure 또는 검증 미완료다. 전체 run이 취소되어 aggregate도 미실행이면 완료로 보지 않는다. +- 다른 workflow를 조회하지 않은 aggregate가 전체 품질의 유일한 권위가 되어서는 안 된다. 필요한 독립 check들은 각각 required로 남기거나 검증된 동일 DAG에 통합한다. 이슈가 workflow 사이 결과를 자동으로 알아낸다고 가정하지 않는다. +- head의 과거 green, 다른 test-merge, dispatch-only 성공으로 최신 PR requirement를 대체하지 않는다. 최신 base 변경도 strict policy에 따라 재검증한다. PR head/base/실제 checkout/test-merge를 별도 필드로 기록한다. +- 문서-only/draft/fork도 필수 검사가 사라지지 않게 R02/R04 trigger를 유지한다. fork 승인 대기는 pending이다. untrusted PR에 production secret을 주거나 privileged event로 검사를 우회하지 않는다. + +### 전환·적용 절차 + +1. 실행 가능한 기존 checks로 부분 보호를 적용할 수 있으나 **부분 보호 != 최종 R01 완료**다. T01/T02/R02/R04가 아직 제공하지 않은 필수 suite는 가짜 green이나 skip job으로 채우지 않는다. +2. rename 또는 reusable workflow 전환 시 **새 실제 context를 PR에서 성공 → 새 requirement 적용/read-back → 기존 context 제거** 순서를 지킨다. 그 사이 전체 품질 gate가 사라지는 빈 구간을 만들지 않는다. obsolete context 때문에 영구 pending이 되는 것도 확인한다. +3. 변경 전 원격 원본·rule source/ID·enforcement·matcher·bypass·legacy/effective와 exact payload diff를 준비한다. 적용 대상 branch, 현재 열린 PR에 미칠 lockout, 적격 reviewer 가용성, 복구 방법을 검토 가능한 상태로 만든 뒤 운영 정책 변경 권한을 판단한다. 이 설계 문서나 admin=true만으로 실제 설정 mutation을 이미 허가받았다고 간주하지 않는다. +4. 적용 직전에 다시 읽어 baseline 이후 다른 관리자가 바꾼 내용을 덮어쓰지 않는다. 변경이 생기면 최소 diff를 다시 계산한다. 원본을 백업했다고 전체를 기계적으로 PUT하여 새 필드/상위 정책을 지우지 않는다. +5. 적용 뒤 API 성공 응답만 보지 않고 ruleset 상세와 main/현재 release·hotfix의 effective rules를 재조회한다. 상위 organization/다른 규칙과의 조합, 제외 패턴, bypass, 최종 context/App/strict 값을 확인한다. +6. 문제가 생겨도 disabled 원본으로 자동 rollback하여 보호를 전부 끄지 않는다. 확인된 문제 delta만 권한 하에 수정하고 전후 결과를 남긴다. 문서에 rollback 명령이 있다는 이유로 무조건 실행하지 않는다. + +## 검증 방법과 실패 행렬 + +main·실제 release/hotfix에 실패 코드를 넣거나 force push·delete·시험 merge를 수행하지 않는다. 먼저 read-only PR mergeability와 effective rule 증거를 수집한다. 차단 동작의 실제 negative가 필요하면 **명시 소유의 disposable target/PR와 동등한 검증 규칙**을 사용해 예상 외 성공도 제품 branch에 영향을 주지 않게 한다. 이 격리 대상 변경 역시 실제 수행할 범위·정리와 운영권한을 먼저 확정한다. 현재 조사에서 그런 branch/PR/ruleset은 생성하지 않았다. + +| ID | 자극·상황 | 기대 결과와 증거 수준 | +|---|---|---| +| RP01 | disabled/list 존재와 effective[] 비교 | disabled를 active 또는 규칙 전체 부재로 오판하지 않음. 원격 readback으로 현 baseline 확인됨. | +| RP02 | main·기존 release/hotfix 및 가상 단일/중첩 이름 | 최종 active 정책의 같은 필수 rule 적용. 비대상 feature/유사 접두사는 불필요하게 잠기지 않음. 가상 matcher는 write 차단 증거가 아님. | +| RP03 | 최신 PR 필수 품질 failure | 해당 PR 검증 불충족. 실제 merge rejection 시험은 격리 target만 사용하고 원 응답을 보존. | +| RP04 | 필수 check pending/missing 또는 workflow filter로 미실행 | 병합 가능으로 성공 처리하지 않음. expected check 목록·event·최신 SHA 기록. | +| RP05 | child skipped/neutral/cancelled 또는 0 tests/report 누락 | 실제 aggregate가 실패; GitHub 기본 skipped 허용에 기대지 않음. 로컬 판정 fixture와 실제 원격 run 증거 구분. | +| RP06 | SHA A green 후 SHA B 새 push | A 결과로 B를 통과시키지 않음. head/base/merge와 check SHA를 함께 readback. | +| RP07 | head는 같고 base만 전진 | strict latest-base 효과와 새 test-merge 검증 필요를 관측. old merge green 재사용 금지. | +| RP08 | 승인 없음·작성자 자신의 승인·최종 pusher만의 승인 | 적격 독립 review 조건 불충족. test 때문에 사용자 identity/승인을 위조하지 않음. | +| RP09 | 기존 승인 후 새 push·미해결 review thread | stale/last-push 및 conversation 정책에 따라 차단. 실제 reviewer 미가용이면 미검증 표시. | +| RP10 | 같은 context의 다른 App/manual status | expected App requirement 미충족. 별도 App 가용성 없는 fixture를 실제 GitHub 차단으로 주장하지 않음. | +| RP11 | check rename/reusable wrapper 변경·중복 context | 새 실제 context 성공 후 전환, latest PR에 요구 결과 존재. 구 context 영구 pending·gate 없는 공백 없음. | +| RP12 | 문서-only/draft/fork·dispatch-only green | 적절 PR event의 필수 check 존재. fork 승인 대기·dispatch-only는 PR 검증 완료가 아님. | +| RP13 | ordinary/admin/app direct update·force/delete 시도 | 상시 bypass 없는 정책. destructive negative는 동등 격리 target에서만; main 설정 증거와 별도로 기록. | +| RP14 | 적용 전 원격 drift·다른 상위 ruleset 추가 | stale payload overwrite 없이 중단·최소 diff 재작성. 조회 실패403/404를 보호 부재로 자동 판정하지 않음. | +| RP15 | 정상 PR 최신 필수 check 전부 실행·성공+독립 승인+thread 해결 | 설정이 정상 변경을 영구 봉쇄하지 않는지 read-only mergeability로 확인. 실제 main merge를 R01 검증의 필수 부수효과로 만들지 않음. | + +원격 write를 실행한 행은 target/PR/ruleset ID, caller 역할, SHA·run/attempt·App/context, API status/error, 전후 ref와 rule 결과를 기록한다. 로컬 fixture만 실행했으면 그 수준으로 기록한다. 격리 branch의 rejection과 main effective rule을 합쳐 **main에 실제 실패 merge를 시도해 차단됨**이라고 쓰지 않는다. 기존 disabled 설정을 재활성화했다는 사실만으로 전체 행 통과를 표시하지 않는다. + +## 의존성과 완료 경계 + +- [R02 #617](https://github.com/DevKor-github/OnTime-front/issues/617): 양 플랫폼 build/native/smoke와 strict aggregate, stable context, PR/merge_group trigger. R01은 suite를 복제하지 않는다. +- [R04 #618](https://github.com/DevKor-github/OnTime-front/issues/618): 공통 quality 정의·실제 context 전환·동일 소스 판정. 보호 성공은 AAB/Play 업로드 성공이 아니다. +- [T01 #615](https://github.com/DevKor-github/OnTime-front/issues/615), [T02 #616](https://github.com/DevKor-github/OnTime-front/issues/616): 실제 필수 suite 및 expected/result. 아직 없는 smoke를 required green으로 대체하지 않는다. 물리 기기 RC 완료는 별도다. +- R03/R05/R06/R07/R08/R09: Widgetbook 배포, deployment 환경/경쟁, 공급망 pin, store read-back, release provenance 및 최종 binary network 경계는 각자의 AC로 남긴다. +- 운영 권한, 실제 reviewer, 선행 suite/check, GitHub 기능 가용성 부재는 해당 행을 OPEN으로 남길 이유다. 필요한 유료 plan 구매나 bypass actor 추가를 자동으로 수행하지 않는다. + +## 완료 기준 + +- [ ] 기존 disabled ruleset2100824와 effective 보호 부재를 구분한 최신 baseline·원본·최소 diff·적용 영향이 보존돼 있다. +- [ ] 권한 있는 실제 적용 후 main/명시 release/hotfix에 active effective rules가 확인되고 단일·중첩 matcher 및 비대상 범위가 검증됐다. +- [ ] PR·독립 적격 reviewer1·latest push/stale approval·대화해결·force/delete금지·strict latest-base가 적용됐으며 상시 admin/app bypass가 없다. +- [ ] 최종 Flutter quality80·release manifest·양 모바일 aggregate의 **실제** 고유 context와 App source가 required이며 적절 PR event의 최신 head/test-merge 검증이 연결된다. +- [ ] R02/R04 aggregate가 skipped/neutral/cancelled/missing/0 cases를 실패로 판정하고 예상 case/report/동일 source를 검증한다. T01/T02 미제공 행이 없다. +- [ ] 실패·미실행·오래된 SHA의 핵심 negative와 독립 review 정책을 증명했고 fixture/격리 remote/main effective 수준을 구분했다. 불가한 행은 통과로 쓰지 않았다. +- [ ] check rename과 base advancement가 보호 공백·영구 pending을 만들지 않으며 정상 최신 PR의 조건 충족 가능성도 확인했다. +- [ ] 설정 변경 이후 상세/effective rules·현재 required check의 authoritative read-back이 보존돼 있다. 조회 실패를 absence로 오판하지 않는다. +- [ ] 관련 없는 제품·store·환경·계정 정책을 바꾸지 않았고 main에 시험 실패코드·destructive probe를 넣지 않았다. + +전체 선행 check가 미구현인 상태에서 기존 두 check를 보호한 결과는 부분 구현이다. 운영 설정 적용 또는 실제 reviewer/검증 증거가 남아 있으면 이 이슈는 OPEN이다. R01 완료는 원격 merge 보호의 완료이며 앱 전체 개선·실제 main 병합·기기 QA·스토어 배포 완료가 아니다. + +## grill-with-docs 실제 전체 문답 + +아래는 전담 agent와 root가 실제로 주고받은 질문·답변·보충이다. root는 사용자에게서 위임받은 결정자로 답했으며 문서 완성 뒤 대화를 재구성하지 않았다. + +### Q1 — 기존 규칙·보호 범위·독립 review·우회 정책 + +**Agent:** +> R01 grill-with-docs 읽고 원격을 새로 확인했습니다(2026-09-24 02:50 UTC). main c7184b2 protected=false/effective=[]/legacy404. 단, ruleset 자체는 없지 않습니다: ID2100824 'require review before merge main'이 disabled, 기본branch 대상, 1승인·stale review 해제·대화해결·force/delete금지·bypass[]입니다. Q1 — 이 기존 ruleset을 재사용해 main과 refs/heads/release/**·hotfix/**에 active 보호를 적용하는 범위로 확정할까요? 권고: PR필수, 최신 push에 작성자/최종pusher와 다른 적격reviewer 1승인, stale approvals해제·대화해결, force push/삭제금지, strict latest-base requiredchecks, admin/app/상시bypass 없음. CODEOWNERS/서명커밋/선형history/merge queue는 이번에 임의 강제하지 않고 queue는 R02/R04 merge_group 지원·실행증거 확보 후 별도선택. 현재 admin권한 true는 실제reviewer 가용성이나 운영승인 증거가 아니며, 혼자 작업해서 승인 불가해도 self-approval/bypass로 통과시키지 않습니다. 기존ruleset 설정을 백업하고 원격 drift 재확인 후 최소 변경하며, 보호 활성화는 구현단계에서 정확 payload와 정책 영향을 검토한 뒤 합니다. 이 범위/1review/우회정책을 채택할까요? + +**Root:** +> Q1 채택합니다. 기존 disabled ruleset ID2100824를 재사용하고 main 및 refs/heads/release/**, refs/heads/hotfix/** 보호를 명시합니다. PR 필수, 최신 변경에 작성자/최종 pusher와 다른 적격 reviewer 1승인, stale approval 해제·대화해결, force/delete 금지, strict latest-base 필수 검사, 상시 admin/app bypass 없음. CODEOWNERS·서명커밋·선형history·merge queue 새 강제는 범위 밖; queue는 merge_group 실제 지원 증거와 별도 운영 선택 후. 현재 admin=true는 reviewer 존재나 활성화 동의의 증거가 아니고 승인 불가를 self-approval/bypass로 우회하지 않습니다. 구현 시 현재 설정 백업·fresh drift 확인·정확한 최소 payload 및 적용 branch/lockout 영향을 준비하고, 실제 저장소 운영 정책 변경은 구체 결과를 검토 가능한 상태에서 적용 권한을 판단해야 합니다. disabled 규칙을 '규칙 자체 없음'으로 기록하지 말고 effective 보호 부재로 정정하세요. 현재 branch가 아직 없으면 matcher 검증과 실제 branch 효과 검증을 구별합니다. + +### Q2 — 실제 check·skip/neutral·App·전환 순서 + +**Agent:** +> R01 Q2 — 필수 검사 선택과 배선 전환을 확정하겠습니다. 현재 exact28baf check-runs는 github-actions App15368의 test=success, release-manifest=success, Widgetbook build_and_preview=skipped입니다. GitHub 공식 문서는 required checks에서 skipped/neutral도 허용하고 job 조건 skip은 성공으로 간주될 수 있다고 명시하므로 ruleset만으로 '실행한 검사만 통과'를 보장할 수 없습니다. 권고: R02/R04가 최종 생성한 고유·안정 check context와 App15368을 실제 PR event/최신 head·test-merge SHA readback으로 고정, Flutter quality80·release manifest·양mobile aggregate 모두 요구(Widgetbook/배포/dispatch-only check 제외). 각 aggregate는 always+실제 필수 child success/예상 case/report/동일source 검사로 skipped/neutral/missing을 failure로 변환해야 하며, 외부workflow결과를 안 읽는 aggregate에 global 성공을 맡기지 않습니다. workflow rename은 새 context를 먼저 실제 PR에서 성공시킨 뒤 보호목록 전환·readback 후 이전context 제거; T01/T02/R02/R04 미완료를 임시green으로채우지않습니다. 현재 실행 가능한 test/release-manifest부터 부분 보호를 활성화할 수 있으나 전체 R01 완료는 최종 필수 검사가 모두 배선돼야 합니다. 최근7일 success조건·중복context·다른App/수동status위조·오래된commit green을 검사하고 App바인딩이 GitHub Actions 내부 workflow 자체변조까지 막지 못함을 명시, workflow/gate수정도 동일review필수로 처리합니다. 이 check/전환 정책으로 갈까요? + +**Agent — 답변 대기 중 matcher 조사 보충:** +> R01 조사 보충: 전체 branch pagination 확인 결과 main 외 release/1.0.0, release/1.0.2-build-54/55 및 hotfix 3개도 protected=false입니다. 가상 release/r01-probe, hotfix/r01-probe의 effective rules도 []입니다(브랜치 생성 안 함). 공식 fnmatch 문서상 단순 *는 /를 넘지 않으므로 최종 payload matcher는 release/hotfix의 단일·중첩 이름 각각 effective API로 확인하고 필요하면 refs/heads/release/**/* 및 hotfix/**/*를 함께 포함하도록 기술하겠습니다. 현재 지정한 ** 문자열을 검증 없이 모든 중첩 branch 커버한다고 주장하지 않겠습니다. 원본 읽기 증거는 /tmp/r01-github-readback.json(SHA256 a3b6981fef9973d12cc3d1924df7677e63457c7f7feb1e8dff55dad4004b0525)입니다. + +**Root:** +> Q2 채택합니다. 현재 실행 증거가 있는 test/release-manifest와 github-actions App15368을 출발점으로 하되 최종 R02/R04 고유 stable context는 실제 PR의 최신 head/test-merge SHA readback으로 확정하세요. 필수 Flutter quality80·release manifest·양 mobile aggregate, Widgetbook/배포/dispatch-only 제외. GitHub required skipped/neutral 허용을 안전으로 오해하지 않게 always aggregate가 child의 실제 success/예상 case/report/source identity를 검증해 skip/neutral/missing/0case를 실패로 바꿉니다. 외부 workflow 결과를 모르는 집계는 전체 성공 권위가 될 수 없습니다. rename은 새 context 실제 PR 성공→required 전환/readback→구 context 제거 순서. 선행 미완료는 가짜 green 금지, 부분 보호와 최종 완료를 구별. 최근7일 success·중복 context·App binding·오래된 SHA/다른 app status 회귀를 포함하되 실제 원격에서 실행하지 못한 공격 fixture는 실제 차단 검증과 구분하세요. Actions App 바인딩은 내부 workflow 변조 방어가 아님도 명시, workflow/gate 변경 동일 review 적용. 부분 활성화의 운영 권한과 범위는 Q1에 정한 구현 시 검토 경계 유지. + +### Q3 — 적용·실패 차단·정상 경로·종결 + +**Agent:** +> R01 Q3 — 검증·적용·종결 기준 권고입니다. 먼저 기존 ruleset+상속/effective/legacy·현재 checks/PRhead/base/merge를 시각·hash와 저장하고 최소 diff를 만듭니다. source와 policy가 바뀌면 재검토하며 기존 disabled 규칙을 무조건 복구하는 자동 rollback은 두지 않습니다(문제가 생기면 확인된 delta만 운영권한 하에 수정). 활성화 뒤 main/현재release·hotfix 및 가상단일·중첩matcher effective readback, bypass없음·review1/latestpush/stale/thread·strict/App/check목록을 검증합니다. negative는 main/실제release에 실패코드나 forcepush/삭제/merge를 시도하지 않고, 필요 시 명시 소유 disposable target+PR에 동등 rule을 적용해 실패/pending/missing/취소/오래된SHA/다른App/checkrename/baseadvance/review누락·stale/대화미해결을 관측합니다. 격리target의 차단과 main의effective설정은 별도증거이며 실제main 실패merge차단을 수행했다고부르지않습니다. 적격reviewer·별도App등가용성 없는행은미검증으로남기고승인위조/우회계정생성없음. 실패 PR mergeability readback을 우선 사용하고, 실제rejection을시험할때도격리target만사용해예상외성공시제품main에영향없게합니다. 완료는 최종 R02/R04 checks가 실제PR에서실행+원격active/effective검증+핵심실패검사의격리차단/최신SHA회귀+review정책증거가갖춰진경우;운영권한/actualreviewer/선행suite가없으면OPEN. 자동머지/스토어배포/새결제plan/새adminbypass는추가하지않습니다. 이기준과 matcher보충을채택할까요? + +**Root:** +> Q3 및 matcher 보충 채택합니다. 현재 disabled/effective/legacy/상속·체크/PR identities를 시각·hash와 보존하고 최소 diff 준비, drift 시 재검토. 단일/중첩 release·hotfix 실제 matcher 효과를 확인해 필요한 명시 pattern을 사용하며 가상 ref 조회와 실제 존재 branch 검증을 구별하세요. 원격 운영 정책 활성화는 Q1의 구체 payload 검토 경계를 유지하며 자동 rollback으로 보호를 조용히 해제하지 않습니다. negative는 실제 main/release에 위험한 push/delete/merge를 하지 않고, 소유 격리 target+동등 rule/PR 및 mergeability 관측으로 검증; 격리 결과와 main effective 상태를 구별. reviewer/별도 App 가용성 없는 행은 미검증, 위조·우회계정·새 유료plan 금지. 최종 선행 checks 실제 PR실행, active/effective/readback, 핵심 실패 및 최신SHA 차단, review정책 증거가 있어야 종결; 권한/선행/reviewer 부족 시 OPEN. 실제 문답과 supplemental matcher 메시지도 전부 보존해 동결해주세요. + +대화의 축약 표기는 원문 그대로 보존했다. 본문은 이 합의를 구현 가능한 기준으로 풀어 쓴 것이다. 이슈 게시·실제 설정 적용 및 검증은 root가 후속 진행하며 현재 문서에서 수행 완료로 표시하지 않는다. diff --git a/plans/audit-2026-09-23/r01-baseline-readback.json b/plans/audit-2026-09-23/r01-baseline-readback.json new file mode 100644 index 00000000..c6104ee3 --- /dev/null +++ b/plans/audit-2026-09-23/r01-baseline-readback.json @@ -0,0 +1,223 @@ +{ + "queried_at": "2026-09-24T02:50:25.044233+00:00", + "source": "28baf6c7b91c00d2c6490f597375c1dad39fc2b6", + "repository": { + "exit": 0, + "data": { + "default_branch": "main", + "full_name": "DevKor-github/OnTime-front", + "permissions": { + "admin": true, + "maintain": true, + "pull": true, + "push": true, + "triage": true + }, + "private": false + }, + "stderr": "" + }, + "main": { + "exit": 0, + "data": { + "commit": "c7184b2ae477e6a24a47fd0b9da79d3ed52b8cf4", + "name": "main", + "protected": false, + "protection": { + "enabled": false, + "required_status_checks": { + "checks": [], + "contexts": [], + "enforcement_level": "off" + } + } + }, + "stderr": "" + }, + "effective": { + "exit": 0, + "data": [], + "stderr": "" + }, + "rulesets": { + "exit": 0, + "data": [ + { + "id": 2100824, + "name": "require review before merge main", + "target": "branch", + "source_type": "Repository", + "source": "DevKor-github/OnTime-front", + "enforcement": "disabled", + "node_id": "RRS_lACqUmVwb3NpdG9yec4ywDwFzgAgDlg", + "_links": { + "self": { + "href": "https://api.github.com/repos/DevKor-github/OnTime-front/rulesets/2100824" + }, + "html": { + "href": "https://github.com/DevKor-github/OnTime-front/rules/2100824" + } + }, + "created_at": "2024-10-04T20:55:30.725+09:00", + "updated_at": "2026-03-19T16:24:38.386+09:00" + } + ], + "stderr": "" + }, + "legacy": { + "exit": 1, + "data": { + "message": "Branch not protected", + "documentation_url": "https://docs.github.com/rest/branches/branch-protection#get-branch-protection", + "status": "404" + }, + "stderr": "gh: Branch not protected (HTTP 404)" + }, + "checks": { + "exit": 0, + "data": { + "check_runs": [ + { + "app": { + "id": 15368, + "slug": "github-actions" + }, + "conclusion": "skipped", + "head_sha": "28baf6c7b91c00d2c6490f597375c1dad39fc2b6", + "name": "build_and_preview", + "status": "completed" + }, + { + "app": { + "id": 15368, + "slug": "github-actions" + }, + "conclusion": "success", + "head_sha": "28baf6c7b91c00d2c6490f597375c1dad39fc2b6", + "name": "release-manifest", + "status": "completed" + }, + { + "app": { + "id": 15368, + "slug": "github-actions" + }, + "conclusion": "success", + "head_sha": "28baf6c7b91c00d2c6490f597375c1dad39fc2b6", + "name": "test", + "status": "completed" + } + ], + "total_count": 3 + }, + "stderr": "" + }, + "disabled_ruleset": { + "exit": 0, + "data": { + "id": 2100824, + "name": "require review before merge main", + "target": "branch", + "source_type": "Repository", + "source": "DevKor-github/OnTime-front", + "enforcement": "disabled", + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "~DEFAULT_BRANCH" + ] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 1, + "dismiss_stale_reviews_on_push": true, + "required_reviewers": [], + "require_code_owner_review": false, + "dismissal_restriction": { + "enabled": false, + "allowed_actors": [] + }, + "require_last_push_approval": false, + "required_review_thread_resolution": true, + "require_extra_approval_for_unattributed_changes": true, + "allowed_merge_methods": [ + "merge", + "squash", + "rebase" + ] + } + } + ], + "node_id": "RRS_lACqUmVwb3NpdG9yec4ywDwFzgAgDlg", + "created_at": "2024-10-04T20:55:30.725+09:00", + "updated_at": "2026-03-19T16:24:38.386+09:00", + "bypass_actors": [], + "current_user_can_bypass": "never", + "_links": { + "self": { + "href": "https://api.github.com/repos/DevKor-github/OnTime-front/rulesets/2100824" + }, + "html": { + "href": "https://github.com/DevKor-github/OnTime-front/rules/2100824" + } + } + }, + "stderr": "" + }, + "protected_targets": { + "exit": 0, + "data": [ + { + "name": "hotfix/fix-calendarscreen-merge-conflict", + "protected": false + }, + { + "name": "hotfix/remove-full-screen-intent", + "protected": false + }, + { + "name": "hotfix/schedule-create-form-next-button-enable", + "protected": false + }, + { + "name": "main", + "protected": false + }, + { + "name": "release/1.0.0", + "protected": false + }, + { + "name": "release/1.0.2-build-54", + "protected": false + }, + { + "name": "release/1.0.2-build-55", + "protected": false + } + ], + "stderr": "" + }, + "effective_release": { + "exit": 0, + "data": [], + "stderr": "" + }, + "effective_hotfix": { + "exit": 0, + "data": [], + "stderr": "" + }, + "raw_readback_sha256": "a3b6981fef9973d12cc3d1924df7677e63457c7f7feb1e8dff55dad4004b0525", + "note": "Read-only baseline. Full paginated historical branch list omitted; protected target rows retained. No rules changed." +} diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index e9fb33df..3d38618f 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -894,16 +894,18 @@ "main 보호와 effective rules가 없다. 검사 성공이 병합의 필수 조건이 아니다.", "PR·최신 커밋 필수 검사와 필요한 review를 ruleset으로 강제. 실패한 검사로 일반 변경이 main에 들어가지 않는지 확인. [GitHub status checks](https://docs.github.com/en/pull-requests/reference/status-checks)" ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_r01", + "issue_number": 619, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/619", "decisions_file": "plans/audit-2026-09-23/issues/R01.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 32 + "sequence": 32, + "readback_verified": true, + "issue_body_sha256": "a6f999305b838cc8d2473a8da0731b0f193ac503293247a6501269a90189844a" }, { "audit_id": "R09", From 84f374de449eb2a36f2555d437bb3df558e8283e Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 12:03:05 +0900 Subject: [PATCH 49/59] docs: verify C01 remote CI and clarify binary audit scope --- handoff/audit-2026-09-23-progress.md | 2 + plans/audit-2026-09-23/README.md | 2 +- plans/audit-2026-09-23/c01-ci-validation.json | 42 +++++++++++++++++++ plans/audit-2026-09-23/issues/C01.md | 6 +++ plans/audit-2026-09-23/issues/T02.md | 10 ++++- plans/audit-2026-09-23/state.json | 10 ++--- 6 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 plans/audit-2026-09-23/c01-ci-validation.json diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 5d1b1967..b58b3fd0 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -128,3 +128,5 @@ A07원격최종28baf6c7 양CI성공: Flutter35947757749=944/analyze88.70%; Andro C01 source4529d9f8, 전체959/88.74%, 소스24/commit 해시 일치, KOEN200%8장 root 확인. 실제 음성 회귀와 추가 문답은 c01-final-validation.json/artifacts/c01에 보존. #600 OPEN: A08 aggregate, A09 staging/runtime/writer fencing, D02 손상 복원 및 실기기 미완료. A08 전담 /root/implement_a08에 Flutter 독점권 전달. R04#618 게시·readback 완료,32/64. R01 전담 /root/grill_r01 문답 진행. PR585 본문 승인 질문은 여전히 미응답이며 동결 payload를 변경/재게시하지 않음. R01 상세 이슈 게시·readback 완료,33/64(미생성31). 기존 rule2100824 disabled/effective없음 fresh read만 수행, 원격 정책 변경 없음. R09 전담 /root/grill_r09 질문 진행. C01 원격 head aa2fda68의 Flutter35949254397/Android35949254443 초기 in_progress 확인. A08 Q1~Q3 schema3/store+aggregate incarnation/생성 전용 revision fence 결정은 a08-implementation-preflight.md와 담당 transcript에 보존. + +C01 원격 최종: exactaa2fda68 Flutter35949254397 959/analyze/88.72%(13504/15221), Android35949254443 성공. artifact10787843483, testmerge e74e6c52d6b1c036f29ac828307a35c9ec04302b, APK a3d2858af8f52adea00b361da6511a8432e62bcdbedfb3bd2e045a86213ee38d. c01-ci-validation.json 보존, #600 통합/기기 미완료 OPEN. T02 no-network 표현은 receiver packaged계약 vs Dart source검사로 정정·#616 readback완료. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 4a442ef3..08d9a1a0 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -29,7 +29,7 @@ | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 독립 journal·복구/host kill11, main통합 원격882 tests/88.50%·APK 통과,기기 대기 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | d06474d4 실제 타이머 전환 구현,47경계 및 원격929전체·88.79%·APK 통과,기기 대기 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | d73c7863 원자적 시작·partial retry 구현,원격944 tests/88.70%·APK 및 KOEN200% 검증,기기 대기 | -| C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 4529d9f8 백업/reset workflow 구현,959 tests·88.74%·KOEN200% 통과,A08/A09/D02 통합 대기 | +| C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 4529d9f8 백업/reset workflow 구현,원격959 tests·88.72%·APK 및 KOEN200% 통과,A08/A09/D02 통합 대기 | | A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 실제4문답·원자 저장/조회·충돌/멱등/소유권 기준 확정 | | A09 | [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | 실제4문답·staging/runtime 세대·복원 후 정리 기준 확정 | | D01 | [#603](https://github.com/DevKor-github/OnTime-front/issues/603) | 실제4문답·DB 비의존 시작 복구·키/파일 보존 기준 확정 | diff --git a/plans/audit-2026-09-23/c01-ci-validation.json b/plans/audit-2026-09-23/c01-ci-validation.json new file mode 100644 index 00000000..29ef8515 --- /dev/null +++ b/plans/audit-2026-09-23/c01-ci-validation.json @@ -0,0 +1,42 @@ +{ + "head_commit": "aa2fda68fbf3ac431738de3792f5d587d35b6dda", + "runs": [ + { + "conclusion": "success", + "databaseId": 35949254443, + "event": "pull_request", + "headSha": "aa2fda68fbf3ac431738de3792f5d587d35b6dda", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35949254443", + "workflowName": "Android Notification Contract", + "artifact_id": 10787843483, + "identity": { + "commit": "e74e6c52d6b1c036f29ac828307a35c9ec04302b", + "artifact": "build/app/outputs/flutter-apk/app-release.apk", + "sha256": "a3d2858af8f52adea00b361da6511a8432e62bcdbedfb3bd2e045a86213ee38d", + "signing": "disposable CI validation key; not a store release", + "merged_manifests": [ + "build/app/intermediates/merged_manifest/release/outputReleaseAppLinkSettings/AndroidManifest.xml", + "build/app/intermediates/merged_manifest/release/processReleaseMainManifest/AndroidManifest.xml", + "build/app/intermediates/merged_manifests/release/processReleaseManifest/AndroidManifest.xml" + ], + "device_delivery_verified": false + } + }, + { + "conclusion": "success", + "databaseId": 35949254397, + "event": "pull_request", + "headSha": "aa2fda68fbf3ac431738de3792f5d587d35b6dda", + "status": "completed", + "url": "https://github.com/DevKor-github/OnTime-front/actions/runs/35949254397", + "workflowName": "Flutter Testing", + "tests_passed": 959, + "coverage": { + "percent": 88.72, + "lines_hit": 13504, + "lines_found": 15221 + } + } + ] +} diff --git a/plans/audit-2026-09-23/issues/C01.md b/plans/audit-2026-09-23/issues/C01.md index 8bba5361..1d02a0c2 100644 --- a/plans/audit-2026-09-23/issues/C01.md +++ b/plans/audit-2026-09-23/issues/C01.md @@ -225,3 +225,9 @@ DB 원자성은 A08/A09/A04/A07의 실제 data transaction에서 보장한다. O ## 구현 담당 반영 freshness/export/preview 실패 경로에 시작 generation 검사를 적용했다. reset은 확인 화면을 열기 전 busy를 획득하고, 확인 직후 route 및 기존 generation을 검사한 다음 명시 reset을 접수한다. 실제 reset 결과는 자체 invalidate를 실패로 오인하지 않는다. old error/freshness/disposed route 회귀를 추가했다. + +### 원격 CI 최종 확인 + +정확한 head `aa2fda68fbf3ac431738de3792f5d587d35b6dda`의 [Flutter run35949254397](https://github.com/DevKor-github/OnTime-front/actions/runs/35949254397)에서 959 tests/analyze와 coverage **88.72% (13504/15221)**를 통과했다. [Android run35949254443](https://github.com/DevKor-github/OnTime-front/actions/runs/35949254443)의 release APK·merged/packaged notification manifest 검사도 성공했다. artifact10787843483의 identity를 내려받아 source/test merge를 구분해 읽었다: 실제 synthetic merge `e74e6c52d6b1c036f29ac828307a35c9ec04302b`, APK SHA256 `a3d2858af8f52adea00b361da6511a8432e62bcdbedfb3bd2e045a86213ee38d`. 일회용 CI 서명이며 `device_delivery_verified: false`다. 근거 `c01-ci-validation.json`. + +로컬 coverage88.74%와 원격88.72%는 각 실행의 실제 값이며 동일 값으로 합치지 않았다. 앞선 기록의 원격 CI 대기는 해소됐지만 A08/A09/D02 통합과 실제 모바일 검증은 남아 있어 #600은 OPEN이다. diff --git a/plans/audit-2026-09-23/issues/T02.md b/plans/audit-2026-09-23/issues/T02.md index a1ca00f4..885943f7 100644 --- a/plans/audit-2026-09-23/issues/T02.md +++ b/plans/audit-2026-09-23/issues/T02.md @@ -17,7 +17,7 @@ | [RunnerTests:8](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/ios/RunnerTests/RunnerTests.swift#L8) | A02가 placeholder를 malformed export argument 검사로 바꿨다. nil, 잘못된 bytes, 빈 bytes, 경로 이름, 잘못된 확장자에 실패 callback 한 번을 검사한다. 이를 아직 placeholder라고 반복하지 않는다. | | [Runner scheme](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme#L50) / 고정 tree의 `android/app/src` | RunnerTests가 TestAction에 연결돼 있다. Android app의 `test`/`androidTest` 테스트 소스는 고정 tree에 없다. scheme 존재·Swift typecheck는 실제 XCTest 실행 결과가 아니다. | | [Mobile Backup Verification](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/docs/Mobile-Backup-Verification.md) | A02 Kotlin 실제 Android/Flutter API 컴파일, Swift iOS simulator target 및 RunnerTests typecheck, Dart/widget 회귀가 기록돼 있다. 문서의 당시 “Android 전체 build 미실행”은 이후 APK CI로 보완됐지만 native picker·교차 복원·XCTest 실행까지 보완된 것은 아니다. | -| [Android Notification Contract workflow](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/.github/workflows/android-notification-contract.yml) | release APK 빌드, packaged/merged manifest, receiver 및 no-network 계약, artifact identity를 검사한다. 일회용 검증 서명이며 `device_delivery_verified: false`이다. 이미 있는 검사를 미구현이라고 쓰지 않는다. | +| [Android Notification Contract workflow](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/.github/workflows/android-notification-contract.yml) | release APK 빌드, packaged/merged manifest의 notification receiver·boot 계약, 별도 Dart local-only source 검사와 artifact identity를 확인한다. packaged 검사기가 INTERNET/ACCESS_NETWORK_STATE 부재나 전체 SDK/runtime 무통신까지 검증하는 것은 아니며 그 확장은 R09다. 일회용 검증 서명이며 `device_delivery_verified: false`이다. 이미 있는 검사를 미구현이라고 쓰지 않는다. | | [A06 CI 증거](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/plans/audit-2026-09-23/a06-ci-validation.json) | source `e6c1438e…`의 host 929개/88.79%, Android APK CI 성공이 있다. APK 검사 artifact는 merge SHA `0896d865d605a9b0966742f9172863b219a6ec5b`, digest `d0f64d4e914bbeb6dcaf7f37e9c62fdaa8f9b35b9b5fc0df673f8fc561c51007`. 이 과거 SHA의 성공을 T02 신규 native suite나 현재 물리 기기 성공으로 합산하지 않는다. | | [Android manifest:52](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/src/main/AndroidManifest.xml#L52), [NativeAlarmPolicy](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmPolicy.kt) | 플러그인 ScheduledNotificationReceiver/BootReceiver와 boot/update/QUICKBOOT action이 추가돼 있다. Android full-screen 승인은 false다. 실기기 테스트 편의를 위해 이 값을 켜지 않는다. | | [MainActivity:40](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt#L40), [취소 read-back:244](https://github.com/DevKor-github/OnTime-front/blob/7a6825f79a565c9b006f3ec57b837271b0e06ba4/android/app/src/main/kotlin/club/devkor/ontime/MainActivity.kt#L244) | 실제 plugin/native channel 등록과 payload sanitize, timing 권한 경계가 있다. Android에는 app-wide AlarmManager 열거가 없다는 계약 아래 reset 전역 취소는 false를 반환한다. known token 취소의 성공을 모든 OS 예약 부재로 확대하지 않는다. | @@ -212,3 +212,11 @@ P11/P17의 key 접근 불가를 “key 없음”으로 바꿔 새 저장소를 **Agent:** > 리뷰 수용합니다. P03은 예정된 Schedule 시작 OS 예약의 전달로 한정하고, A06 active periodic 단계 알림은 별도 행에서 연속 실행 가능한 paused 관측만 허용하며 suspend/kill 정시 보장·resume/catch-up 재생이 없다고 명시하겠습니다. P04에 A05 #588을 연결하고 이 리뷰 원문과 현재 답변을 그대로 문서 말미에 추가하겠습니다. 기존 확정 계약의 표현 정정이며 새 정책 결정은 아닙니다. + +### 2026-09-24 R09 조사에 따른 검사 범위 정정 + +**R09 agent 실제 보충:** +> 추가 baseline 정정 근거: T02.md 근거 표의 'receiver 및 no-network 계약' 표현은 현재 tool/check_android_notification_manifest.py가 INTERNET/ACCESS_NETWORK_STATE를 검사한다는 뜻으로 읽히면 과장입니다. 고정 aa2fda68에서 해당 Python은 boot/receiver만 검사하고, 별도 Dart source checker가 main manifest INTERNET만 확인합니다. R09 본문에서 이 정확한 수준을 명시하겠습니다. T02 원문은 제 소유 밖이므로 수정하지 않습니다. + +**Root 실제 답변:** +> T02 표현 보충 채택합니다. 해당 workflow가 local-only Dart source checker도 실행하므로 no-network 검사가 전혀 없다는 뜻으로 바꾸지 않고, packaged/merged 단계는 notification receiver/boot contract, no-network는 source scanner 수준이라고 root가 T02 표를 명확히 수정하겠습니다. INTERNET/ACCESS_NETWORK_STATE 부재를 최종 APK 전체 manifest에서 이미 입증했다고 표현하지 않습니다. 이 실제 보충 문답도 R09에 보존해주세요. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 3d38618f..6ad3d730 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -471,20 +471,20 @@ "Full Flutter 959 passed; app-owned coverage88.74% (13507/15221); analyzer clean", "Actual stale-preview baseline failure and Drift race/rollback/receipt regressions verified", "KO/EN 200% eight widget captures reviewed; source24/commit hashes match", - "Generated/local-only/manifest and Python11 passed" + "Generated/local-only/manifest and Python11 passed", + "Exact aa2fda68 remote959 tests/analyze, coverage88.72% (13504/15221), Android APK/notification manifest success; artifact10787843483 identity read back" ], "blockers": [ "A08 schedule aggregate FormSubmission integration is pending; no atomic aggregate completion claim.", "A09 staging Local Data Store, runtime/session replacement and all-writer fencing are pending. Candidate remains authenticated parsed memory data, not verified staging.", "Revision is checked before OS cleanup and again inside DB transaction; a racing edit during cleanup prevents DB overwrite but may leave OS cleanup pending. Receipt exposes this and retry reconciles current data only.", "D02 damaged-database restore integration is pending.", - "No actual Android/iOS picker, encrypted file roundtrip, provider delivery or reset device proof in this change.", - "Remote CI pending" + "No actual Android/iOS picker, encrypted file roundtrip, provider delivery or reset device proof in this change." ], "sequence": 14, "readback_verified": true, "body_sha256": "2fb0876dfbbef2ee14038ab85bf1fb4e9bb724197551a76889309c93a19d8c1b", - "issue_body_sha256": "e882ebf132325c47df8890eb51f5514af5fb23020e264bdddcc11a4bb9374650" + "issue_body_sha256": "34d437455542e2abd79d7b7abfb7cb735f5a714fab8b15b0bce25e053e0f1139" }, { "audit_id": "A08", @@ -836,7 +836,7 @@ "blockers": [], "sequence": 29, "readback_verified": true, - "issue_body_sha256": "e44eecda9ac22ab41a5068261b0e86acdf7325081cdb4fa0ef3cd4ad467ab31b" + "issue_body_sha256": "4b97820ffb4043a4ea1af525c2d3d857aab7884b455be3b5351cc18cb2bf21f2" }, { "audit_id": "R02", From 9682c34e8fa7c5a804e789cbbcfcb18f3d883601 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 12:07:36 +0900 Subject: [PATCH 50/59] docs: define final mobile artifact network verification --- handoff/audit-2026-09-23-progress.md | 2 + plans/audit-2026-09-23/README.md | 3 +- plans/audit-2026-09-23/issues/R09.md | 207 +++++++++++++++++++++++++++ plans/audit-2026-09-23/state.json | 16 ++- 4 files changed, 220 insertions(+), 8 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/R09.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index b58b3fd0..44ecaafd 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -130,3 +130,5 @@ C01 source4529d9f8, 전체959/88.74%, 소스24/commit 해시 일치, KOEN200%8 R01 상세 이슈 게시·readback 완료,33/64(미생성31). 기존 rule2100824 disabled/effective없음 fresh read만 수행, 원격 정책 변경 없음. R09 전담 /root/grill_r09 질문 진행. C01 원격 head aa2fda68의 Flutter35949254397/Android35949254443 초기 in_progress 확인. A08 Q1~Q3 schema3/store+aggregate incarnation/생성 전용 revision fence 결정은 a08-implementation-preflight.md와 담당 transcript에 보존. C01 원격 최종: exactaa2fda68 Flutter35949254397 959/analyze/88.72%(13504/15221), Android35949254443 성공. artifact10787843483, testmerge e74e6c52d6b1c036f29ac828307a35c9ec04302b, APK a3d2858af8f52adea00b361da6511a8432e62bcdbedfb3bd2e045a86213ee38d. c01-ci-validation.json 보존, #600 통합/기기 미완료 OPEN. T02 no-network 표현은 receiver packaged계약 vs Dart source검사로 정정·#616 readback완료. + +R09#620 상세 이슈 게시·원문 readback 완료,34/64(미생성30). O01 /root/grill_o01 문답 착수. 관리 파일에서 A02/A14 본문 해시가 과거값인 것을 발견해 GitHub를 새로 읽었고 현재 local body와 remote가 정확히 같음을 확인, state hash만 갱신(게시내용 변경 없음). A08 actual 준비 INSERT fault로 partial schedule row가 남는 기존 결함 재현, 구현 진행 중. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 08d9a1a0..18ee3d60 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -49,8 +49,9 @@ | R02 | [#617](https://github.com/DevKor-github/OnTime-front/issues/617) | 실제3문답·동일 PR 소스 양모바일 build/native/smoke·최소권한/실패판정·12행 CI 검증 | | R04 | [#618](https://github.com/DevKor-github/OnTime-front/issues/618) | 동일 품질 재사용·검증 AAB와 전송 바이트 연결, 16개 검증 행렬과 실제 3문답 확정 | | R01 | [#619](https://github.com/DevKor-github/OnTime-front/issues/619) | 실제 3문답·비활성 ruleset 재사용/최신 소스 검사·독립 리뷰·15개 검증 행렬 확정 | +| R09 | [#620](https://github.com/DevKor-github/OnTime-front/issues/620) | 실제 3문답·최종 APK/IPA 구성/오프라인/트래픽 귀속·20개 검증 행렬 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **33/64개**를 게시하고 원격 본문 동일성을 확인했으며 **31개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **34/64개**를 게시하고 원격 본문 동일성을 확인했으며 **30개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/R09.md b/plans/audit-2026-09-23/issues/R09.md new file mode 100644 index 00000000..be57aec6 --- /dev/null +++ b/plans/audit-2026-09-23/issues/R09.md @@ -0,0 +1,207 @@ +# [P1][R09] 최종 Android·iOS 산출물의 SDK·권한·서명 설정과 오프라인·런타임 네트워크 경계 검증 + +## 문제와 결과 목표 + +소스의 금지 문자열 검사가 통과하고 Android APK가 빌드되어도, 최종 앱에 어떤 SDK와 권한이 포함됐는지, 처음 실행할 때 외부 연결을 요구하는지, 연결 가능한 환경에서 앱이 요청을 시도하는지는 별도 문제다. 현재 검사는 그 증거들을 모두 제공하지 않는다. + +R09는 **정확한 release candidate의 정적 구성, 새 설치 오프라인 핵심 여정, 연결 가능한 환경의 앱 자체 네트워크 관측**을 서로 구별해 기록하고 출시 판정에 연결한다. Android·iOS strict local-only, 로그인·서버·원격 analytics 없음이라는 기존 Product Network Boundary를 유지한다. 유한한 관측을 모든 미래 실행에서의 무통신 또는 전체 보안 감사 완료로 표현하지 않는다. + +- 우선순위 **P1**, 감사 R09. 전담 subagent `/root/grill_r09`가 `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`를 읽고 질문을 한 번에 하나씩 전달했다. root가 사용자를 대신해 답했다. +- 고정 조사 source: **`aa2fda68fbf3ac431738de3792f5d587d35b6dda`**. 아래 코드 링크는 이 소스 기준이며 병행 작업 트리의 제품 변경이나 후속 CI 성공을 포함하지 않는다. +- 이번 작업은 AGENTS·CONTEXT·ADR0024·감사와 R02/R04/T01/T02 이슈, 고정 소스·기존 artifact 기록 및 공식 문서를 읽고 **이 파일만 작성**했다. 앱/CI/기기/build/run/트래픽 수집/GitHub/스토어 작업은 하지 않았다. +- **현재 상태: 상세 설계 완료, R09 신규 검사·새 실행 증거는 미구현·미실행.** 문답 원문과 보충 정정은 말미에 모두 보존한다. + +## 고정 근거와 현재 검사 수준 + +| 근거 | 현재 확인한 사실 | 남은 검증 | +|---|---|---| +| [ADR0024](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/docs/adr/0024-enforce-no-network-in-product-builds.md), [CONTEXT](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/CONTEXT.md) | Product Network Boundary는 사용되지 않는 remote client까지 제거하고, Offline Cold Start와 명시적인 OS external navigation을 구별한다. | 기존 정책을 실행 증거로 검증한다. 네트워크 허용 예외나 새 도메인 용어를 만들지 않는다. | +| [source checker:3](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/tool/check_local_only_boundary.dart#L3) | lib의 특정 package import·문자열 URL, pubspec 직접 dependency, 일부 native Firebase/remote notification 설정을 검사한다. | direct socket/네이티브 API, constructed endpoint, transitive dependency, 실제 포함된 SDK, 동적 초기화 경로를 완전히 탐지하는 분석기가 아니다. | +| [source checker:44](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/tool/check_local_only_boundary.dart#L44) | Android **main source manifest**의 INTERNET 문자열을 검사한다. | ACCESS_NETWORK_STATE와 최종 merged/packaged의 전체 permission 검사라고 표현하지 않는다. | +| [Android Notification Contract](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/.github/workflows/android-notification-contract.yml), [packaged 검사기:33](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/tool/check_android_notification_manifest.py#L33) | workflow는 source local-only checker, 실제 release APK build 및 packaged/merged XML 검사를 실행한다. **Python XML 판정은 notification receiver/boot contract**다. | INTERNET/ACCESS_NETWORK_STATE 부재를 최종 APK에서 강제한 증거로 확대하지 않는다. R09가 모든 module/split에 이 검사를 추가한다. | +| [Android main manifest](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/android/app/src/main/AndroidManifest.xml), [debug](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/android/app/src/debug/AndroidManifest.xml), [profile](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/android/app/src/profile/AndroidManifest.xml) | main에는 알림·timing·boot·진동 및 backup 제외 설정이 있고 debug/profile은 개발 transport용 INTERNET을 선언한다. | 개발용 선언 자체를 제품 위반으로 처리하지 않는다. release variant에 합쳐지는 경우는 차단한다. | +| [iOS entitlements](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/ios/Runner/Runner.entitlements), [Info.plist](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/ios/Runner/Info.plist) | source에는 time-sensitive notification entitlement, 앱 scheme·AlarmKit 설명 등 설정이 있다. | source 파일은 서명된 앱의 effective entitlement가 아니다. entitlement 일반 존재를 outbound 허가로 간주하지 않는다. | +| [pubspec](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/pubspec.yaml), [lock](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/pubspec.lock), [Podfile.lock](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/ios/Podfile.lock) | 직접 product dependency와 lock 정보는 존재한다. 고정 CocoaPods lock은 Flutter·SQLCipher를 열거한다. | Dart lock/Pods 하나만으로 실제 native/SwiftPM/Gradle/plugin/정적 링크 전체를 열거했다고 하지 않는다. 최종 bundle inventory와 교차 확인한다. | +| [A07 기존 CI receipt](https://github.com/DevKor-github/OnTime-front/blob/aa2fda68fbf3ac431738de3792f5d587d35b6dda/plans/audit-2026-09-23/a07-ci-validation.json) | source28baf6c7의 Flutter944/88.70%, Android35947757745 성공과 artifact10786864301을 기록한다. APK hash는 c060bad4…db29, 실제 merge는5d57fde3…e431이다. | 일회용 CI key, `device_delivery_verified:false`다. aa2fda68 자체의 runtime traffic, store candidate, 물리 기기 증거가 아니다. | + +**감사 정정:** Android release APK와 merged/packaged manifest CI는 이미 존재한다. 반대로 그 workflow 이름이나 local-only source gate가 존재한다는 이유만으로 최종 permission·SDK·runtime 무통신 검증까지 완료했다고 말하지 않는다. T02의 이전 “receiver 및 no-network 계약” 표현도 source와 package 단계를 분리해 해석한다. root가 그 표를 별도로 정정하기로 했다. + +고정 소스 검색에서 `url_launcher` dependency나 outbound `launchUrl` 구현은 확인하지 못했다. 현재 `openURLContexts`는 inbound 앱 진입 코드이며 외부 navigation 구현 증거가 아니다. 허용된 external navigation은 정책 경계일 뿐 R09가 새 링크 기능을 만드는 요구가 아니다. 실제 구현이 없는 조건부 행은 그 근거와 `not_applicable`로 남기고 존재하는 경로를 skip하는 데 쓰지 않는다. + +## 공식 근거와 기술적 한계 + +- Android [apkanalyzer](https://developer.android.com/tools/apkanalyzer)는 실제 APK의 manifest·permission·DEX·파일 구성을 읽는 도구다. 목록은 조사 입력이며 난독화된 모든 SDK와 호출 도달성을 자동 증명하지 않는다. +- Android [Network Inspector](https://developer.android.com/studio/debug/network-profiler)는 지원 네트워크 라이브러리 범위가 제한된다. 그 화면이 비었다는 사실은 Dart/native socket 요청 부재의 증거가 아니다. +- Android [emulator command line](https://developer.android.com/studio/run/emulator-commandline)은 virtual Ethernet packet capture를 설명한다. 실제 사용 도구 버전의 지원 옵션을 확인하고 해당 경로 밖의 loopback/별도 interface·프로세스 귀속 한계를 기록한다. 최신 문서를 이유로 locked toolchain을 임의 교체하지 않는다. +- Apple [packet trace](https://developer.apple.com/documentation/network/recording-a-packet-trace?changes=l_1_6)는 iOS RVI가 기기 전체 네트워크 stack을 나타내며 interface metadata를 구분할 수 있다고 설명한다. 앱 한 개의 trace라는 가정은 금지한다. [troubleshooting](https://developer.apple.com/documentation/network/troubleshooting-packet-traces?changes=_3_7&language=objc)은 capture 손실·local traffic 관측 문제를 검토할 근거다. 검색으로 읽힌 본문 범위를 사용했고 도구 실행 성공을 주장하지 않는다. +- Apple [ATS](https://developer.apple.com/documentation/security/preventing-insecure-network-connections?changes=l_5)는 연결의 보안 조건을 강제하는 기능이다. 안전한 HTTPS와 저수준 API까지 모든 outbound를 차단하는 firewall이 아니다. +- Apple [Entitlements](https://developer.apple.com/documentation/bundleresources/entitlements?language=obj_8)는 최종 서명 시 구성이 결합된다는 근거이며 [distribution entitlement 검사](https://developer.apple.com/library/archive/qa/qa1798/)는 실제 bundle 서명과 profile을 읽는 접근의 근거다. 보관된 Q&A의 플랫폼별 예시 명령을 현 iOS 도구에서 실행했다고 주장하지 않는다. +- Apple [privacy manifest](https://developer.apple.com/documentation/bundleresources/privacy-manifest-files) URL은 확인했으나 직접 open에서 본문은 로드되지 않았다. 이 링크를 API 세부 검증으로 인용하지 않는다. PrivacyInfo의 유무 자체가 network 차단 증거가 아니라는 판정은 기존 ADR0024의 소스·artifact·runtime 구분을 적용한 것이다. + +## 확정 계약 1 — 정확한 최종 구성 검사 + +### Android + +1. 실제 후보 AAB의 모든 module manifest와 R04가 그 AAB에서 만든 APK set의 모든 split을 열거한다. base manifest만 읽고 완료하지 않는다. 단독 APK 후보면 실제 단독 APK와 설치 파일의 관계를 확인한다. +2. XML namespace와 permission node 종류를 구조적으로 파싱한다. INTERNET·ACCESS_NETWORK_STATE는 `uses-permission`, SDK 조건부 선언 등 어느 지원 variant에도 없어야 한다. 주석의 문자열을 권한으로 판정하거나 `maxSdkVersion`으로 조건이 붙었다고 금지 권한을 허용하지 않는다. merge source provenance도 기록한다. +3. DEX package/class inventory, native `.so`·assets/config/plugin registrar, Gradle resolved dependency와 Dart lock/도구 입력을 연결한다. Firebase/Auth/analytics/remote HTTP client·server 설정 등 기존 금지 항목은 dormant 상태라도 제거 대상이다. +4. 직접 `HttpClient`/Socket/WebSocket 등의 Dart 호출, native network API와 third-party 자동 초기화·background entrypoint를 조사한다. 생성 문자열/동적 도달성을 좁은 regex만으로 완전히 증명하지 않는다. 분석 불능·미분류 의존성은 pending으로 남겨 수동 근거를 요청하고 자동 allowlist에 추가하지 않는다. +5. release debug transport·VM service용 구성 잔입을 확인한다. source debug/profile 권한을 삭제하는 대신 release에 미포함임을 입증한다. Flutter3.44.4/Dart3.12.2 및 기존 지원 하한을 유지한다. + +### iOS + +1. 실제 signed IPA를 풀어 main app·포함 extension·embedded framework/동적 라이브러리 및 정적 링크 목록을 확인한다. extension이 없다면 “없음”의 enumeration 증거를 기록한다. +2. 각 실행 bundle의 **effective signed entitlements**, 실제 Info.plist, embedded profile의 필요한 공개 식별자·capability, privacy manifests, plugin 등록·native dependency·SDK 목록을 확인한다. source Runner.entitlements만 검사하지 않는다. +3. 기존 정책과 어긋나는 remote push/auth/CloudKit 등 capability 또는 remote SDK가 있으면 근거와 사용 경로를 확인해 차단한다. time-sensitive/AlarmKit/설치별 secure storage 등 정당한 local 기능을 무조건 금지하지 않는다. 자동 생성 signing 식별자와 민감 provisioning 원문은 구분한다. +4. ATS 예외·Bonjour/local networking·WebView/remote loading·URLSession/Network/CFNetwork 호출 등 조사 대상을 명시한다. 해당 flag가 없다는 이유만으로 outbound가 불가능하다고 하지 않는다. iOS에는 Android INTERNET 제거와 같은 일반 outbound permission 차단을 이 계획에서 가정하지 않는다. +5. engine/OS framework가 제공하는 일반 networking symbol만 발견됐다는 이유로 앱 위반이라 단정하지 않는다. 해당 빌드의 app-owned 호출과 third-party 초기화 도달 경로, 일반 framework 제공 코드의 구별 근거를 기록한다. 반대로 이름을 바꾼 unknown SDK를 안전하다고 추정하지 않는다. + +**공통:** inventory에는 artifact 경로/hash, component 이름·버전·origin/lock 관계, 근거 도구, 분류(`allowed_local|prohibited|needs_review`), 판단 이유와 검토 source를 남긴다. 허용 표는 버전/소스별이며 광범위 engine/domain 예외로 변경을 숨기지 않는다. 최종 bytes 안에 폰트·이미지·KO/EN 문구·timezone rules·필수 legal text가 포함되는지 T01의 실제 오프라인 표시 결과와 연결한다. + +## 확정 계약 2 — 오프라인 동작과 연결 가능한 runtime 관측 + +두 실행은 다른 속성을 검증한다. 비행기 모드라 packet이 없다는 사실로 연결 가능한 환경의 자발 송신을 판정하지 않는다. + +| 실행 | 사전 조건·행동 | 필수 판정 | +|---|---|---| +| A. Offline Cold Start | 첫 launch 전에 airplane mode, Wi-Fi/cellular 재활성화 없음. warm cache/connected resource download 없는 새 소유 설치. | T01의 동일 exact candidate onboarding·local CRUD·준비/완료·delivery 설정·backup/preview/restore/reset·legal/core resource 여정을 재사용. 네트워크 없이 완료되며 기존 데이터 손상이 없음. | +| B. Connected observation | 별도 새 소유 QA 설치와 연결 가능한 통제된 네트워크, 외부 관측 도구가 시작된 상태에서 첫 launch. | cold start/onboarding/CRUD/준비·로컬 알림/backup preview·restore/reset/background·resume/PID relaunch 동안 app-origin request/attempt와 미분류 flow 확인. | + +### 관측 창과 환경 유효성 + +- 초기 기본값은 앱 실행 전 baseline **60초**, 실제 모든 core 상호작용 구간, background/lock **5분**, resume 및 종료 뒤 각각 **60초**다. 실제 시작/끝과 순서·permission/provider·PID를 기록한다. 시간은 검증 watchdog/관측 범위이며 제품 수명주기 규칙을 바꾸지 않는다. 이보다 늦은 timer/조건부 송신까지 부재라고 주장하지 않는다. +- 관측 전후 별도 known-traffic fixture가 **소유 local controlled sink**로 합성 요청을 보내 capture가 이를 보는지 확인한다. fixture app/process·destination·시각/독립 sink receipt를 제품 trace와 구별한다. fixture를 OnTime binary에 주입하지 않고 외부 공개 endpoint나 사용자 계정으로 보내지 않는다. +- 실제 target ID·OS/patch·모델/ABI·app ID·artifact/signing·capture tool 버전·interface·filter·route/connectivity·positive control·drop counter와 가용 UID/PID attribution을 기록한다. IPv4/IPv6, DNS/TCP/UDP/QUIC, local/loopback 경계를 다룬다. 지원하지 않는 interface/protocol/권한은 명시 사각지대이고 전체를 확인했다고 하지 않는다. +- 잘못된 interface, positive control 미검출, capture 중단/손실, 필수 결과 부재는 `blocked` 또는 `fail`이다. 빈 pcap/0 packets만으로 pass하지 않는다. HTTP proxy 또는 특정 library inspector 하나만으로 우회 경로까지 관측했다고 말하지 않는다. +- 앱이 outbound를 **시도**한 경우 전송이 OS permission/firewall 때문에 막혔어도 위반이다. 다만 패킷이 나오지 않은 denied attempt를 pcap만으로 탐지했다고 주장하지 않는다. 가용 OS 진단·source 도달 경로·실제 외부 trace의 증거 수준을 구별한다. 미확인 시도까지 전부 없었다는 보증은 금지한다. + +### 앱·OS·provider 귀속과 false positive + +- iOS RVI/device packet trace는 OS와 다른 process의 통신을 함께 포함한다. Android external capture도 IP만으로 앱 UID를 식별했다고 가정하지 않는다. PID/UID·지원되는 process metadata·실제 interaction timeline·component 식별과 독립 기록을 연결한다. +- baseline 통신량을 빼거나 Apple/Google domain을 통째로 allowlist해서 앱 요청을 지우지 않는다. background OS traffic이 보였다는 이유만으로 OnTime 실패라고 단정하지도 않는다. **귀속 불명 traffic은 `unresolved`이며 필요한 runtime 판정은 pending**이다. +- User-Initiated External Navigation이 실제 구현돼 있다면 탭 전 자동 fetch 없음, 명시 OS browser/mail/store 위임, OnTime 데이터가 URL/query에 포함되지 않음을 확인한다. offline destination 실패 후 앱의 데이터·navigation이 보존돼야 한다. browser가 만든 traffic은 근거를 남겨 구분하며 앱 내부 fetch를 browser traffic으로 면제하지 않는다. +- backup export/import의 기본 성공 여정은 local document provider다. 사용자 명시 선택 OS provider의 외부 작업은 위임 이벤트/프로세스·시각으로 구분한다. cloud provider의 원격 동기화 완료는 OnTime의 보장/증거가 아니며 이를 위해 제품 client/permission을 추가하지 않는다. 현재 외부 기능이 없으면 분류기 fixture만 검증하고 실제 제품 여정의 성공으로 바꾸지 않는다. +- 사용자 기기의 raw traffic, 실제 계정/사용자 DB/키/Backup Password를 수집하지 않는다. TLS MITM·사용자 CA 설치·release instrumentation·debug transport 추가로 측정 편의를 확보하지 않는다. 관측에 필요한 권한/귀속 수단이 없으면 제한을 기록하고 OPEN을 유지한다. + +## 확정 계약 3 — artifact identity와 CI·기기 역할 + +| 단계 | 필수 연결 | 한계 | +|---|---|---| +| R04 Android 후보 | exact AAB hash→bundletool/device spec→파생 APK set/각 split hash→실제 설치 identity, 공개 서명 certificate 식별자 | 별도 flutter APK나 test APK의 결과를 후보 AAB에 붙이지 않는다. local validation signing은 Play App Signing이 아니다. | +| R08 iOS 후보 | exact signed IPA hash→app/extension 파일 manifest·CodeDirectory/UUID·서명종류→실제 설치 app ID/version/build/확인 가능한 식별자 | thinning/resign/재패키징 후 byte 동일을 주장하지 않는다. 원본과 설치본 관계가 확인되지 않으면 candidate 검증 pending. | +| PR CI | 기존 source policy + native dependency inventory + 실제 package permission/구성 검사 + parser/gate negative + 소유 emulator/simulator 검증 가능한 행 | unsigned/simulator/disposable key는 물리 signed RC가 아니다. R01은 PR 자동 check를 요구한다. | +| RC release gate | 지정 물리 Android와 iOS에서 같은 signed candidate의 offline+connected 필수 증거·정적 inventory·검증기 결과 | 물리 결과를 모든 PR에서 실행해야 하는 check와 혼동하지 않는다. R04가 해당 후보 release 판정에 연결한다. | + +receipt의 최소 필드: + +- source head/base/실제 checkout 또는 merge SHA, workflow/run/attempt/job, 검증기와 정책 버전, Flutter/Dart/native toolchain·lockfile hashes. +- artifact ID·원본 파일/각 설치 component SHA256·package/bundle ID·version/build·서명종류 및 공개 certificate/서명 식별자, 변환/설치 경로. GitHub archive digest와 내부 AAB/IPA digest를 구별한다. +- 대상/OS/patch/ABI, clean-install 근거, scenario/fixture ID, locale/timezone, provider/permission, 시작/끝·관측 창, expected/actual과 driver/injection. +- static inventory·명시 금지 항목·unknown, capture tool/interface/filter·protocol coverage·drop/control·PID/UID/귀속 근거·unresolved, case별 `pass|fail|blocked|not_run|not_applicable`와 사유. +- evidence 경로/hash, 기존 T01/T02 실행을 재사용하는 정확한 ID, 원 실패와 재실행 관계, 미검증 범위. source나 artifact가 다른 기존 성공은 재사용하지 않는다. + +required result 누락·skip/pending·identity 불일치이면 **최종 release gate 미충족**이다. PR 정적 검사 성공과 필수 물리 gate pending을 별도 상태로 유지한다. 같은 이름의 green, latest artifact, branch 이름만으로 후보를 선택하지 않는다. 다운로드 뒤 실제 파일을 재해시하고 receipt·설치 identity를 read-back한다. + +### 증거 보관과 데이터 최소화 + +공개/원격 artifact는 필요한 최소 metadata·합성 fixture 진단·hash·통계·원 실패/후속 결과를 기본30일 보존한다. raw pcap은 기본 게시하지 않는다. 전용 기기여도 OS/provider payload가 포함될 수 있으므로 검토에 꼭 필요하면 소유 로컬 제한 보관 후 정리하고 요약과 원본 hash의 관계만 남긴다. 키·비밀번호·서명 private key·production DB·개인 식별 provisioning 원문·사용자 payload는 저장소/CI artifact/cache에 넣지 않는다. redaction 전 원본을 공개한 뒤 삭제하는 방식은 사용하지 않는다. + +새 disposable AVD/simulator 및 명시 지정 QA 물리 기기만 사용한다. 기존 simulator `C8BA159D-A97A-458B-A5FC-B04A8B6F2848`, 사용자 설치, 다른 작업의 target/호스트 네트워크는 변경하지 않는다. target을 자동으로 첫 연결 기기로 선택하지 않는다. 문서 작성 시 기기/서명 가용성은 새로 조회하지 않았으며 실행 직전 확인한다. + +## 구현 순서와 구체 검증 행렬 + +1. 기존 source/notification 검사와 R04/R08 identity schema를 고정한다. R02/T01/T02 제공 entrypoint·기기·서명 가용성을 확인하고 dependency pending을 드러낸다. +2. 실제 APK/AAB module/split, signed IPA/app/extension의 구성 inventory와 parser를 구현한다. source/lock/native 의존성 및 최종 구성의 차이를 출력하고 unknown을 차단한다. +3. 검사기 fixture와 실제 package 입력을 연결한다. 기존 source scan은 강화하되 새로운 문자열 규칙을 완전한 도달성 분석으로 부르지 않는다. +4. 외부 관측·target isolation·positive control·귀속/결과 schema를 만들고 전용 known-traffic fixture로 탐지 가능성을 실제 한 번 검증한다. 제품 binary에는 송신코드를 추가하지 않는다. +5. 같은 candidate의 T01 offline 여정을 재사용하고 연결 가능한 별도 설치를 측정한다. emulator/simulator 조기 검사 후 물리 양 플랫폼 RC를 수행한다. +6. 원격 artifact 다운로드/hash·같은 소스/설치 identity·필수 행 완전성을 확인하고 R01/R04/O02/R08에 receipt를 연결한다. 사각지대·unresolved·환경 실패가 남으면 완료로 닫지 않는다. + +아래 행은 **이 문서 작성 시 모두 미실행**이다. fixture 판정과 실제 플랫폼 capture, signed physical RC는 각각의 증거 수준으로 기록한다. + +| ID | 자극·조건 | 독립 판정과 실패 기준 | +|---|---|---| +| NET01 | 최종 Android base/module/split 중 하나에 INTERNET 또는 ACCESS_NETWORK_STATE | namespace/node 구조 검사로 차단. source main이 깨끗해도 최종 gate 실패. | +| NET02 | SDK 조건부 permission·debug/profile manifest가 release로 혼입 | 조건부/variant를 숨김으로 쓰지 못함. 개발 variant 자체는 false positive 없이 유지. | +| NET03 | direct forbidden import·Dart/native socket 호출 또는 조합 URL fixture | 해당 좁은 정책 검사/수동 경로 검토로 탐지·차단, constructed 표현 전체를 완전 탐지했다고 주장하지 않음. | +| NET04 | transitive prohibited SDK/config/자동 initializer가 최종 binary에 포함 | 직접 pubspec만 깨끗한 경우도 차단. 실제 artifact component와 dependency origin 연결. | +| NET05 | 이름 변경/미분류 SDK, lock과 inventory 불일치 | unknown/pending으로 release 차단, 문자열 미일치만으로 허용 안 함. | +| NET06 | 일반 Flutter/OS network symbol, local notification entitlement | symbol/entitlement 존재만으로 오판하지 않고 origin·app 호출/초기화 근거로 구분. blanket allowlist 금지. | +| NET07 | source에는 없지만 signed app/extension에 prohibited capability·remote hook | effective signed 구성 검사에서 차단. 정상 자동 서명 식별자와 local capability는 구별. | +| NET08 | 깨진 manifest/plist, 누락 split/extension inventory, unsigned를 signed로 표시 | parser/evidence 실패. 일부만 읽은 결과 또는 빈 목록을 전체 성공으로 처리 안 함. | +| NET09 | source/head/merge/run attempt/artifact hash가 다름 | candidate 결과 연결 거부. 새 bytes는 새 검증. thinning/resign 변환 근거 없으면 pending. | +| NET10 | clean install airplane mode core 여정 | 같은 signed candidate의 T01 core가 최초 offline 상태에서 완료. 폰트/legal/timezone 등 첫 다운로드 필요하면 실패. | +| NET11 | 연결 가능한 cold start·core·background/lock·resume·relaunch | 지정 관측 창 안의 app-origin flow/attempt 없음, 실제 trace와 귀속 기록. window 밖 보장 없음. | +| NET12 | known-traffic 별도 fixture→소유 local sink | capture 전후 control 탐지와 독립 sink receipt 일치. 제품 binary가 control을 송신하지 않음. | +| NET13 | empty/missing capture, wrong interface, capture 중단, control 미검출 | 0 packets/exit0이어도 실패 또는 blocked. 네트워크 단절을 무통신 pass로 바꾸지 않음. | +| NET14 | drop·filter 누락·IPv6/UDP/QUIC 또는 loopback 관측 불능 | coverage/손실 진단, 관련 필수 관측 pending. HTTP inspector만 성공으로 대체 불가. | +| NET15 | OS background flow와 app-origin 또는 귀속 불명 flow fixture | OS traffic을 앱 실패로 오판하지 않고 app flow를 domain allowlist로 지우지 않음. unresolved는 gate pending. | +| NET16 | 명시 external browser/OS provider flow, 탭 전 자동 요청 fixture | 명시 위임과 앱 내부 fetch 구별. 자동 요청·데이터 URL 첨부는 실패. 실제 기능 없으면 조건부 N/A 근거. | +| NET17 | permission 거부로 packet이 나오지 않는 app network attempt fixture | OS 진단/도달경로 증거로 위반 분류. pcap만으로 blocked attempt 탐지했다고 하지 않음. | +| NET18 | PCAP/진단에 사용자 데이터·비밀·provider payload 후보 | 원격 게시 차단/최소 metadata로 제한, 민감 원본 기본 미수집·미게시. | +| NET19 | emulator/unsigned/disposable signing 결과를 물리 RC로 대체 | evidence level 불일치로 release gate 미충족. 각 물리 signed candidate 실행 필요. | +| NET20 | 필수 receipt/report missing, skipped/cancelled 또는 이전 green 재사용 | 전체 완료/출시 성공 불가. 원 실패와 재실행 보존, 정확 source/artifact read-back 필요. | + +NET01–09/13–20의 parser/gate fixture 성공은 실제 원격 package 실패나 기기 traffic 탐지 결과와 구별한다. NET12는 별도 fixture의 실제 관측 검증이다. 악성 네트워크 코드를 제품 branch/release에 넣거나 사용자의 연결을 가로채서 negative를 증명하지 않는다. 같은 사례를 무한 반복하거나 실패 case를 빼고 green으로 만들지 않는다. + +## 의존성·완료 기준 + +- [T01 #615](https://github.com/DevKor-github/OnTime-front/issues/615): 정상 main/startup/DI/실제 암호화 저장소 여정과 offline clean install. exact candidate가 같을 때 기존 실행을 공유한다. +- [T02 #616](https://github.com/DevKor-github/OnTime-front/issues/616): native permission/provider/키·picker·실제 physical boundary. pcap은 실제 알림/파일 완료의 대체 증거가 아니다. +- [R02 #617](https://github.com/DevKor-github/OnTime-front/issues/617): source·package/native·소유 emulator 검사 배선. suite/capture 도구 미제공은 pending으로 남긴다. +- R04: 같은 AAB의 derived 설치 및 검증한 bytes의 배포 연결. R09 receipt의 RC 필수 결과를 최종 release gate에 반영한다. 실제 store upload 실행은 별도 권한/작업이다. +- R01: 실제 PR 자동 check enforcement. 물리 RC 실행을 모든 PR 필수 check로 바꾸지 않는다. +- R08: signed IPA·release manifest·symbol/서명·변환 identity 및 보관. 이름만 같은 다른 artifact 성공을 결합하지 않는다. +- O01/O02/O03/O05: 현재 제품 설명·privacy/store 신고·진단·release stop 조건에 실제 증거와 한계를 연결한다. R09 완료만으로 store 신고 변경·법률 적합성·사용자 배포를 완료했다고 하지 않는다. + +- [ ] 기존 source gate와 Android packaged/merged notification contract를 유지하고 각 증거 범위의 과장된 baseline 표현을 정정했다. +- [ ] Android 전체 candidate module/split의 최종 permission·DEX/native SDK 구성과 iOS signed app/extension의 effective entitlement·Info/framework/SDK inventory가 정확 source/lock/artifact에 연결된다. +- [ ] 금지 API/SDK·자동 초기화 및 unknown을 검사하고 benign engine/OS symbol을 맹목적으로 금지하거나 허용하지 않는 근거가 있다. +- [ ] 양 물리 플랫폼의 같은 signed candidate에서 첫 실행 전 offline core와 별도 connected runtime 관측을 완료했고 window·drop/control·귀속·사각지대를 기록했다. +- [ ] positive control 실제 탐지와 false negative/positive fixture가 있으며 empty trace·wrong interface·unresolved를 pass로 처리하지 않는다. +- [ ] external navigation/document provider의 명시 위임·app-origin을 구분하고 URL 데이터 유출·제품 네트워크·자동 cloud analytics를 추가하지 않았다. +- [ ] source/actual merge/run attempt와 AAB/IPA→설치 identity를 원격 다운로드·재해시/read-back으로 확인했고 thinning/resign 또는 다른 binary를 같은 bytes로 부르지 않는다. +- [ ] R01 PR 자동 check와 R04 RC gate가 필수 missing/skip/pending을 성공으로 처리하지 않으며 CI와 physical evidence level을 구별한다. +- [ ] 합성 전용 대상·최소 증거만 사용했고 사용자 기기/호스트 네트워크/다른 QA 환경을 보존한다. raw pcap은 기본 미게시, 비밀·실사용 데이터는 artifact/cache에 없다. +- [ ] 모든 필수 static/runtime/물리 행에 실제 결과가 있고 unknown attribution·기기/서명 부재·capture 한계로 unresolved인 핵심 행이 없다. 남으면 **R09 OPEN**이다. + +제외: 새 서버·로그인·remote analytics·network permission, 제품에 송신 테스트 코드/CA/MITM/debug 우회 추가, 사용자 트래픽 수집, 무한 OS/OEM 조합, 모든 미래 요청 부재의 수학적 보증, 전체 보안 감사·스토어 제출·병합·사용자 배포 완료 주장. 새 glossary/ADR는 필요하지 않다. + +## grill-with-docs 실제 전체 문답 + +질문·권고와 root 답변은 실제 교환 원문이다. 본문 결정에 맞추어 소급 합성하지 않았으며 보충과 정정도 보존한다. + +### Q1 — strict boundary의 판정 범위 + +**Agent:** +> R09 Q1 — strict Product Network Boundary의 판정 범위를 확정하겠습니다. 권고: 기존 source scanner와 Android merged/packaged manifest CI를 유지하되 후자는 현재 notification receiver만 검사하므로 INTERNET/ACCESS_NETWORK_STATE 부재를 packaged 단계에서 검증했다고 확대하지 않습니다. 최종 candidate의 모든 Android split/AAB module manifest·DEX/native SDK 목록과 iOS signed app/extension의 실제 entitlements·Info.plist·embedded frameworks/SDK/PrivacyInfo를 source/lock inventory와 대조하고 unknown은 수동 근거 확인 전 pending으로 둡니다. prohibited SDK/API·remote hooks는 차단하되 Flutter engine/시스템 framework에 일반 네트워크 symbol이 있다는 이유만으로 SDK 위반으로 단정하지 않고 실제 app-owned 호출·third-party 초기화 경로를 조사합니다. iOS에는 outbound 전체 차단 permission이 없고 ATS·privacy manifest 무항목도 차단 증거가 아니므로 정적 검사+offline core+connected controlled traffic 관측을 별도 필수로 둡니다. 명시 OS external navigation과 사용자가 선택한 document provider는 앱 runtime 요청과 분리하고, OnTime 데이터는 외부 URL에 담지 않으며 cloud provider의 동기화 완료를 주장하지 않습니다. 기존 glossary/ADR0024를 재사용하고 ‘영구 무통신 수학적 증명’ 대신 해당 정확 artifact의 위반 부재·관측 범위/한계를 기록하는 안입니다. + +**Root:** +> Q1 채택합니다. 기존 source와 Android packaged/merged notification contract의 실제 검사 범위를 정확히 구별하고, 최종 전체split/AABmodule manifest의 INTERNET/ACCESS_NETWORK_STATE 부재는 R09 추가 검증으로 명시. DEX/native/SDK 및 iOS signed app+extension entitlements/Info/framework/privacy inventory를 source/lock과 연결하고 unknown은 근거 확인 전 pending. 일반 engine/OS network symbol 존재만으로 위반 판정하지 말고 app-owned 호출과 third-party 초기화 도달 경로를 조사하세요. iOS ATS/PrivacyInfo/entitlement 부재는 outbound 차단 증거 아님, static/offline core/연결상태 통제된 traffic 관측 각각 필요. 외부 URL/사용자선택 OS document provider는 명시 위임과 앱 요청을 구별, OnTime 데이터 URL전달 금지·provider cloud 동기화 완료 주장 금지. 정확 artifact/source·측정 시나리오·관측 창·프로세스/OS 귀속 및 사각지대 기록, 유한 관측으로 영구 무통신 증명 금지. 기존 ADR0024/glossary 재사용, 제품 네트워크 허용 예외를 새로 만들지 않습니다. + +### Q2 — 유한 관측과 오탐·미탐 + +**Agent:** +> R09 Q2 — runtime 측정의 유한 범위와 false negative/positive 통제를 확정할까요? 권고: 같은 release candidate의 (A) 새 설치·첫 실행 전 airplane/Wi-Fi/cellular OFF core 여정은 T01 증거 재사용, (B) 별도 새 소유 QA 설치에서 연결 가능 상태의 cold start→onboarding/CRUD→준비/로컬 알림→backup preview/restore/reset→background/resume·PID relaunch를 외부 capture로 관측합니다. 시작 전 baseline 60초, 핵심 상호작용 전체, background/lock 5분·resume/종료 후 60초를 초기 bounded window로 기록하고 이보다 늦은 지연 송신은 보증하지 않습니다. iOS RVI는 device 전체 stack이므로 process metadata 가용성·PID/UID·타임라인 등 근거로 앱/OS/provider/driver를 분류, 단순 baseline 빼기·Apple domain allowlist로 앱 요청을 지우지 않습니다. 귀속 불명 traffic은 unresolved로 전체pass 불가. Android Network Inspector는 HttpsURLConnection/OkHttp 한정이므로 Dart/native socket 무관측을 성공으로 쓰지 않고 외부 packet capture+가용 UID attribution을 사용합니다. DNS/TCP/UDP/QUIC/IPv4/IPv6와 capture drop/interface/연결성은 검사, blocked connect attempt도 app-owned면 위반. TLS MITM/CA설치·제품 instrumentation/network추가 없이 synthetic 전용 기기만, baseline·positive control(별도 fixture가 local controlled sink로 알려진 요청)을 전후 관측해 잘못된 interface/죽은 capture를 실패로 만듭니다. public remote endpoint 호출·사용자 raw traffic 수집은 하지 않고 필요한 metadata/합성 fixture 증거만 남기는 안입니다. + +**Agent 조사 보충:** +> 추가 baseline 정정 근거: T02.md 근거 표의 'receiver 및 no-network 계약' 표현은 현재 tool/check_android_notification_manifest.py가 INTERNET/ACCESS_NETWORK_STATE를 검사한다는 뜻으로 읽히면 과장입니다. 고정 aa2fda68에서 해당 Python은 boot/receiver만 검사하고, 별도 Dart source checker가 main manifest INTERNET만 확인합니다. R09 본문에서 이 정확한 수준을 명시하겠습니다. T02 원문은 제 소유 밖이므로 수정하지 않습니다. + +**Root:** +> Q2 채택합니다. A offline fresh install/core는 같은 exact candidate T01 증거 재사용, B 연결 가능한 별도 소유 QA 설치의 명시 여정을 외부 관측. baseline60초·전체 상호작용·background/lock5분·resume/종료후60초 초기 유한 window를 기록하고 이후 지연송신 무보증. Android Inspector의 HTTP한정/Flutter socket 사각과 iOS RVI devicewide 한계를 명시, process/UID/PID·타임라인·가용 네트워크 계측을 함께 사용. baseline차감/Apple domain blanketallowlist로 앱요청 제거 금지, 귀속 불명은 pending. DNS/TCP/UDP/QUIC v4/v6, interface/drop/연결성 및 전후 positivecontrol로 capture유효성 검사. 앱 outbound 시도도 위반이지만 permissiondenied 등 packet이 안 나온 시도를 외부pcap만으로 탐지했다고 주장하지 말고 OS진단/정적 도달경로와 구별하세요. 긍정대조는 별도 fixture→소유 local controlled sink, 제품 binary 수정/CA/MITM/새 network권한 금지. 사용자가 쓰는 기기의 raw traffic 수집 금지, 합성 전용 target만. 측정권한/도구 사각으로 핵심 귀속이 안 되면 OPEN, 무패킷만 PASS 금지. + +**Root baseline 정정 보충:** +> T02 표현 보충 채택합니다. 해당 workflow가 local-only Dart source checker도 실행하므로 no-network 검사가 전혀 없다는 뜻으로 바꾸지 않고, packaged/merged 단계는 notification receiver/boot contract, no-network는 source scanner 수준이라고 root가 T02 표를 명확히 수정하겠습니다. INTERNET/ACCESS_NETWORK_STATE 부재를 최종 APK 전체 manifest에서 이미 입증했다고 표현하지 않습니다. 이 실제 보충 문답도 R09에 보존해주세요. + +### Q3 — artifact identity·CI/RC·negative·종결 + +**Agent:** +> R09 Q3 — artifact identity·CI/RC 종결 및 negative 행렬을 확정할까요? 권고: R04 exact AAB→derived installed split chain과 R08 signed IPA→실제 app CodeDirectory/UUID/서명·파일 manifest를 재사용하고 sourcehead/base/actualmerge/runattempt/toolchain/lock/artifactID·SHA256/서명종류·device/OS·capture tool/filter/interface·관측창/positivecontrol·귀속/unknown·drop·결과를 하나의 receipt로 연결합니다. iOS thinned/resigned 설치본을 원 IPA byte 동일이라고 하지 않고 확인 가능한 설치 식별자를 기록하며 검증 불가면 pending. CI는 source/lock/native dependency inventory, 실제 package permissions/entitlements, parser/gate negative·소유 emulator capture까지 조기 검사; 물리 Android+iOS exact signed RC의 offline+connected 관측은 필수 release 행으로 두고 simulator/unsigned/pr disposable key로 대체하지 않습니다. negative는 source direct socket/constructed endpoint, transitive prohibited SDK·split-only INTERNET·signed-only entitlement, SDK 이름 위장/unknown, wrong artifact/source/attempt, empty/missing capture·잘못된interface/positivecontrol 실패·drop·unattributed flow를 차단하고 benign engine symbols/explicit external browser·provider traffic을 blanketfail하지 않는 분류 fixture도 둡니다. 실제 known-traffic 별도 fixture 탐지를 한 번 입증하되 production app에 송신코드 넣지 않음. 필수 static/runtime receipt 미존재·pending/skip은 R01/R04의 release gate 통과 불가; store 제출은 별도. raw pcap은 전용 합성대상이라도 앱외 payload 위험이 있어 기본 게시하지 않고 필요한 축약 metadata·해시·진단을30일보존, raw가필요하면소유local제한보관후정리. 모든필수행 actual증거+원격artifact readback까지 없으면R09OPEN, 전체보안감사/모든미래traffic무보증으로 동결하는 안입니다. + +**Agent 조사 보충:** +> R09 원문 문답 3개 및 보충을 본문에 포함해 작성 준비 중입니다. source 정적검사는 SDK 명칭 문자열뿐 아니라 app-owned direct socket/API·transitive/native inventory와 좁은 판정 범위를 기록하고, constructed endpoint·obfuscation을 완전 탐지한다고 보장하지 않는 방향입니다. 현재 Android debug/profile INTERNET은 개발 도구용으로 존재하며 release 잔입만 차단합니다. fixed pubspec에 외부 URL launcher가 없고 source에도 outbound launch 구현을 찾지 못해, external navigation 행은 현재 구현 유무를 재확인하는 정책 조건부 행으로 두고 R09가 새 외부링크 기능을 추가하지 않겠습니다. + +**Root:** +> Q3 채택합니다. R04의 exact AAB→derived 설치 split chain, R08의 signed IPA와 실제 설치 CodeDirectory/UUID/서명·파일 manifest 관계를 재사용, thinning/resign시 원IPA와 byte동일 주장 금지. source/head/base/merge/runattempt/lock/toolchain/artifact ID/hash/서명/기기OS/계측툴·인터페이스·필터·관측창/control/drop/귀속unknown을 receipt에 연결. CI 정적/실제package/negative·소유emulator와 양 물리 signedRC offline+connected 필수release행을 구별하고 unsigned/simulator/disposablekey로 대체불가. missing/skip/pending은 최종release gate 미충족, 실기기 결과를 모든PR 실행조건으로 혼동하지 말고 R01 PR checks와 R04 RC release gate 각 역할에 연결하세요. negative목록 및 benign분류 채택, knowntraffic는 별도fixture만. rawpcap 기본비게시, 합성대상도 앱외payload 주의, 공개metadata는 필요한 최소값/해시30일, raw필요시 소유local제한보관·정리. signed entitlement는 맥락에 맞는 prohibited capability 판정이며 entitlement일반존재를 outbound허가로 일반화하지 말 것. 실제증거/원격artifact readback 없으면OPEN, 유한관측과 전체보안감사의 한계 명시. 충분하면 실제전체문답·보충정정·상세실패행렬·AC로 동결해주세요. + +문답의 축약 표기는 원문 그대로 보존했다. 본문은 이를 구체적인 실행/실패/증거 계약으로 풀어 쓴 것이다. 새 제품 정책이나 네트워크 예외를 도입하지 않았으며 구현·이슈 게시·검증과 authoritative read-back은 root가 이어간다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 6ad3d730..658f982a 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -68,7 +68,7 @@ "Full iOS build and native test execution pending" ], "sequence": 2, - "issue_body_sha256": "e0b8fca33ca5913263230ae4c264a496eed9e745265475adb4063d8b04f0bfba", + "issue_body_sha256": "0fccad866304a88f2acdbb514d4983077e304c3964569841620dd6599eff8ed7", "readback_verified": true, "body_sha256": "0fccad866304a88f2acdbb514d4983077e304c3964569841620dd6599eff8ed7" }, @@ -308,7 +308,7 @@ "Actual Android/iOS delivery and permission/Doze/reboot matrix not executed." ], "sequence": 9, - "issue_body_sha256": "23769c09503d629f813df5b93db7b2b6fce715dd15ea1571ea09fa06b9c1a5d4", + "issue_body_sha256": "61cd6d895147c01b682e9871307d355c4b2bf5847627f6f72a00c6909d97d495", "readback_verified": true, "body_sha256": "61cd6d895147c01b682e9871307d355c4b2bf5847627f6f72a00c6909d97d495" }, @@ -917,16 +917,18 @@ "no-network 체크는 소스 문자열/일부 설정 검사다. 통과만으로 final APK/IPA의 전체 SDK·merged manifest·runtime traffic을 증명하지 못한다.", "final artifact 권한·SDK·entitlement 검사와 비행기 모드 cold start. 앱 자체 트래픽을 별도로 관찰. 사용자 명시적 외부 navigation은 제품 정책에 따라 구분." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_r09", + "issue_number": 620, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/620", "decisions_file": "plans/audit-2026-09-23/issues/R09.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 33 + "sequence": 33, + "readback_verified": true, + "issue_body_sha256": "63d1d3871729e6945649398d5c843489ef750dc9be2602a6765f858400489704" }, { "audit_id": "O01", From b5c4522b397eebcf803edf35a78f2b038dcc0af8 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 12:15:50 +0900 Subject: [PATCH 51/59] docs: define current local-only documentation and setup verification --- handoff/audit-2026-09-23-progress.md | 4 + plans/audit-2026-09-23/README.md | 5 +- plans/audit-2026-09-23/issues/O01.md | 194 +++++++++++++++++++++++++++ plans/audit-2026-09-23/state.json | 18 ++- 4 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/O01.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 44ecaafd..711f4cf4 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -132,3 +132,7 @@ R01 상세 이슈 게시·readback 완료,33/64(미생성31). 기존 rule2100824 C01 원격 최종: exactaa2fda68 Flutter35949254397 959/analyze/88.72%(13504/15221), Android35949254443 성공. artifact10787843483, testmerge e74e6c52d6b1c036f29ac828307a35c9ec04302b, APK a3d2858af8f52adea00b361da6511a8432e62bcdbedfb3bd2e045a86213ee38d. c01-ci-validation.json 보존, #600 통합/기기 미완료 OPEN. T02 no-network 표현은 receiver packaged계약 vs Dart source검사로 정정·#616 readback완료. R09#620 상세 이슈 게시·원문 readback 완료,34/64(미생성30). O01 /root/grill_o01 문답 착수. 관리 파일에서 A02/A14 본문 해시가 과거값인 것을 발견해 GitHub를 새로 읽었고 현재 local body와 remote가 정확히 같음을 확인, state hash만 갱신(게시내용 변경 없음). A08 actual 준비 INSERT fault로 partial schedule row가 남는 기존 결함 재현, 구현 진행 중. + +A08 진행 메모: root는 schema3/store+aggregate identity 외에 안정된 recurring rootSegmentId, template/definition/default 의존 version, 정확 정수분 input digest, create 보호 metadata 거부, pending delivery 이후 currentness, 같은Bloc 다른form callback ownership, Drift trigger의 watch invalidation을 추가 리뷰했다. 실제 전체 문답은 구현 담당이 /tmp/a08-review-transcript.md에 취합 중이다. 최초 negative test는 준비 INSERT 실패 후 schedule row 잔존을 실제 관찰(/tmp/a08-negative.log), 현재 소스/테스트/ARB는 WIP이며 커밋 금지, final sourcefreeze/full validation 아직 없음. + +O01#621 실제3문답·파일inventory/모바일실행·15개검증 기준 게시/readback완료,35/64(미생성29). O02 전담 /root/grill_o02 시작. A08 초기 analyzer compile error0, 경고14 정리 중; historical schema2는 baseline4529 실제DB DDL 추출, schema1 ccc6272와 차이 확인. 집중/전체 최종검증 아직 미완료. source freeze 전 WIP는 구현agent 소유, root Flutter금지. 마지막 검증된 원격 제품은 C01 aa2fda68이며 현재 로컬 docs HEAD9682c34e 이후 관리문서 추가 중. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 18ee3d60..ecaf7d27 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -30,7 +30,7 @@ | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | d06474d4 실제 타이머 전환 구현,47경계 및 원격929전체·88.79%·APK 통과,기기 대기 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | d73c7863 원자적 시작·partial retry 구현,원격944 tests/88.70%·APK 및 KOEN200% 검증,기기 대기 | | C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 4529d9f8 백업/reset workflow 구현,원격959 tests·88.72%·APK 및 KOEN200% 통과,A08/A09/D02 통합 대기 | -| A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 실제4문답·원자 저장/조회·충돌/멱등/소유권 기준 확정 | +| A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 구현 중: 원자 저장/조회·schema3 최소 receipt·충돌/멱등, 실제 partial-write 결함 재현 | | A09 | [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | 실제4문답·staging/runtime 세대·복원 후 정리 기준 확정 | | D01 | [#603](https://github.com/DevKor-github/OnTime-front/issues/603) | 실제4문답·DB 비의존 시작 복구·키/파일 보존 기준 확정 | | D02 | [#604](https://github.com/DevKor-github/OnTime-front/issues/604) | 실제4문답·손상 DB 후보 검증/활성 쌍 교체·중단 복구 기준 확정 | @@ -50,8 +50,9 @@ | R04 | [#618](https://github.com/DevKor-github/OnTime-front/issues/618) | 동일 품질 재사용·검증 AAB와 전송 바이트 연결, 16개 검증 행렬과 실제 3문답 확정 | | R01 | [#619](https://github.com/DevKor-github/OnTime-front/issues/619) | 실제 3문답·비활성 ruleset 재사용/최신 소스 검사·독립 리뷰·15개 검증 행렬 확정 | | R09 | [#620](https://github.com/DevKor-github/OnTime-front/issues/620) | 실제 3문답·최종 APK/IPA 구성/오프라인/트래픽 귀속·20개 검증 행렬 확정 | +| O01 | [#621](https://github.com/DevKor-github/OnTime-front/issues/621) | 실제 3문답·현행/역사 문서 inventory·두 모바일 재현·15개 검증 행렬 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **34/64개**를 게시하고 원격 본문 동일성을 확인했으며 **30개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **35/64개**를 게시하고 원격 본문 동일성을 확인했으며 **29개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/O01.md b/plans/audit-2026-09-23/issues/O01.md new file mode 100644 index 00000000..4033423d --- /dev/null +++ b/plans/audit-2026-09-23/issues/O01.md @@ -0,0 +1,194 @@ +# [O01][P1] 개발·아키텍처·출시 문서를 local-only 제품과 일치시키고 두 모바일의 새 checkout 실행 경로 검증 + +## 문제와 기대 결과 + +새 개발자가 README를 따르면 현재 소스에 없는 API 환경 파일과 Firebase 설정을 준비하고 Web을 제품으로 실행하게 된다. 출시 담당자는 동일한 Release Checklist 안에서 “제품에 API/Firebase 설정이 없어야 한다”와 “Firebase 설정·로그인·FCM 등록을 확인하라”를 동시에 요구받는다. generated Dart를 commit하라는 문장도 저장소의 현행 ignore 정책과 충돌한다. + +현행 개발·아키텍처·출시 진입 문서는 Android/iOS local-only 제품의 실제 코드와 검증 수준을 설명해야 한다. 과거 서버 제품 자료와 실행 증거는 지우거나 현재 성공으로 재해석하지 않고 역사 자료로 구분한다. 새 문서만 따라 깨끗한 checkout에서 두 모바일의 정상 진입·일정 저장·실제 프로세스 재실행을 재현한 뒤 완료한다. 문서 변경 자체는 앱 수정, 실제 기기 QA, 서명 릴리스, 스토어 제출·승인·배포 완료가 아니다. + +- 우선순위: P1. 개발 시작 실패, 잘못된 비밀 설정 요구, 제거된 제품 기능을 전제로 한 출시 판단을 유발한다. +- 원 감사: `docs/App-Improvement-Audit-2026-09-23.md:141`, 총 64개 감사 항목 중 O01. +- 고정 조사 소스: [`aa2fda68fbf3ac431738de3792f5d587d35b6dda`](https://github.com/DevKor-github/OnTime-front/tree/aa2fda68fbf3ac431738de3792f5d587d35b6dda). +- 이 이슈 작성은 source/document read-only 조사와 root 대리 문답이다. 이 이슈의 새 checkout·모바일 실행·문서 검사·외부 문서 게시를 이미 수행했다고 주장하지 않는다. +- A08의 현재 schema 3 작업은 조사 소스의 출시 사실에 포함하지 않는다. 고정 소스 `lib/core/database/database.dart:55`는 schema 2다. 구현 시 새 SHA를 고정해 다시 확인한다. + +## 확인한 근거 + +아래 경로와 줄 번호는 위 고정 SHA 기준이다. 변경 후에는 새 SHA·줄 번호·명령 결과를 다시 기록한다. + +| 근거 | 실제 확인한 충돌 또는 경계 | +|---|---| +| `README.md:5,68–129,182–187,210–221,296–314` | Web을 지원 제품으로 소개하고 `REST_API_URL`, Google Sign-In define, Firebase 파일을 요구한다. 반면 `:237,249`는 Android/iOS 제품의 API/Firebase 금지를 명시한다. | +| `docs/Architecture.md:10,37,55,80–87,116–138,248–298` | remote API·login·Dio·Firebase·Crashlytics·remote-only schedule persistence를 현재 구조로 설명한다. 예시 schema 3도 고정 소스 schema 2와 다르다. | +| `docs/Android-Release-Smoke-Test.md:16–21,35–40,57–60,66,102–126` | 테스트 계정·승인 endpoint·Firebase 프로젝트·FCM 등록·로그인/로그아웃을 통과 조건과 증거 양식에 요구한다. | +| `docs/Release-Checklist.md:40–41,77–78,127,131–136,150–156,257–270,305` | generated commit 지시, endpoint·Firebase·Web 제품 배포·로그인·서버 token 확인이 현행 local-only 선언과 공존한다. | +| `docs/Release-Rollout-Monitoring.md:19–21,27–31,56–67,88–94,103–121,143–144` | 운영 접근 권한·중단 조건·복구 방법·관측 양식이 Firebase/FCM/backend를 필수로 전제한다. | +| `docs/Android-Release-Configuration.md:3–9,23–47,65–91,99` | 제거된 Firebase secret·REST_API_URL·Google Services Gradle 검증 작업을 요구한다. | +| `docs/Android-Manifest-Permissions.md:44–60` | 과거 merged manifest의 INTERNET/FCM/auth 의존 권한을 현행 허용 목록처럼 제시한다. 과거 검사 증거는 보존하되 새 binary 허용 근거로 사용하지 않아야 한다. | +| `docs/Home.md:14–22,35–38` | 낡은 설정·서버 API·analytics 문서를 현행 시작 경로에 노출하고 iOS 안내도 필수 Dart defines로 설명한다. | +| `.github/copilot-instructions.md:226,262`, `AGENTS.md:4,15` | Firebase 초기화·옵션 파일 또는 app Chrome 실행을 현행 작업 지침으로 소개한다. 실제 모듈/지원 플랫폼과 일치하도록 정리해야 한다. | +| `docs/Logging-Policy.md:21–27`, `docs/Error-Handling-Result-System.md:16,52,497–538,593–625` | Dio 로깅과 production Crashlytics 자동 보고가 현재 동작인 것처럼 기술돼 있다. `lib/core/logging/app_logger.dart:11,14,25`는 debug-only다. | +| `docs/Git.md:85–103` | 제품 runtime `ENV` define으로 branch/environment를 분리하도록 설명한다. 배포 track과 제품 원격 환경 설정을 혼동한다. | +| `docs/Notification-Behavior.md:3,9,25–43,79–82,97–106,128–153` | 2026-03-21 FCM/Web push 기록을 “current”로 소개한다. 날짜가 존재해도 현행 진입점에서는 과거 자료임을 명시해야 한다. | +| `docs/adr/0011`, `0016`, `0024` | accepted local-only 정책, Android/iOS만 지원, 제품 network boundary와 개발 debug transport·명시적 외부 navigation의 차이가 이미 정해져 있다. 새 정책을 만들 필요가 없다. | +| `docs/adr/0001`, `0006` | 이미 `status: superseded by ADR-0011`이다. 과거 ADR을 현재 정책으로 되돌리거나 중복 배너를 무조건 삽입하지 않는다. 나머지 관련 ADR도 각 상태를 직접 확인한다. | +| `pubspec.yaml`, `lib/main.dart:18–51` | 현재 의존성과 bootstrap→Recovery/DI→로컬 앱 진입을 확인했다. README가 지시하는 `lib/core/constants/environment_variable.dart`는 해당 SHA에 존재하지 않는다. | +| `.gitignore:49–54`, `AGENTS.md:21,29`, `tool/check_generated_dart_policy.dart` | g/config/freezed/mocks 출력은 ignored/untracked여야 한다. 기존 정책 검사기가 이미 있어 새 상충 규칙을 만들 필요가 없다. | +| `.github/workflows/firebase-hosting-pull-request-widgetbook.yml:17,43–51` | Firebase Hosting은 별도 `widgetbook/` catalog에 사용 중이다. 제품 Firebase 제거를 이유로 이 합법적인 개발 도구 문맥까지 삭제하면 안 된다. | + +## 적용할 문서 계약과 책임 + +1. `README.md`는 새 개발자의 단일 시작 경로다. 제품 지원은 Android/iOS, Web/desktop 폴더 존재는 제품 지원 근거가 아니다. Widgetbook의 dependency install/codegen/Chrome/Hosting 경로는 별도 catalog 절로 구분한다. +2. `docs/Architecture.md`는 해당 SHA의 실제 소스 구조·데이터 흐름을 설명한다. encrypted Local Data Store, Local Profile, startup Recovery, domain workflow/receipt, OS notification/alarm, durable 데이터와 재구성 가능한 runtime의 경계를 실제 경로에 연결한다. 미구현 목표는 명시적으로 구현 대기다. +3. 현행 release/smoke 문서는 요구사항·절차와 실행 결과를 구별한다. “필수 확인”과 “확인 완료”를 혼용하지 않는다. 실제 source, artifact digest, build/OS, 명령, 시각, 결과, 증거 링크가 있는 행만 통과다. +4. `CONTEXT.md`는 용어집, ADR은 정책 결정, 날짜 있는 QA 자료는 당시 증거다. CONTEXT에 파일·명령·test count·구현 메모를 추가하지 않는다. 중복 Schedule/Preparation/Preparation Step 정의를 발견했지만 O01에서 새 의미를 만들거나 전체 용어집을 임의 재설계하지 않는다. +5. historical/superseded 문서는 적용 시점, 현재 제품에는 적용되지 않는 범위, 대체 정책/현행 문서 링크를 첫 부분에 둔다. 과거 원문과 실제 증거는 보존한다. 기존 경로 보존을 우선하고 이동이 꼭 필요하면 원래 위치에 명시적 대체 링크를 둔다. +6. 현재 문서가 인용하는 정책과 코드가 다르면 코드의 부족함을 숨기지 않는다. 정책상 목표·현재 동작·연결 이슈·검증 대기를 함께 적는다. 아직 없는 기능을 문장만 바꿔 현재 기능으로 만들지 않는다. + +| 소유 범위 | O01에서 수행 | 별도 완료 책임 | +|---|---|---| +| O02 개인정보·Data safety·스토어 신고 | 기존 서버 모델 설명임을 표시하고 현재 제출 근거가 아님/O02 검증 미완료를 명시. 현행 진입점 링크도 같은 상태를 전달 | 실제 데이터 흐름/법적 문구/공개 정책/스토어 양식/화면 read-back. O01은 법률 적합성 또는 신고 완료를 판정하지 않음 | +| O03 로컬 진단·지원 | 현재 debug-only logger와 자동 원격 수집 없음의 코드 근거를 설명. 새 진단은 미구현·연결 이슈 | 비식별 오류코드·실패 단계·명시적 내보내기 등 실제 제품/지원 기능 | +| O05 운영 책임·중단 기준 | Firebase/backend 의존 문구 제거. 아직 확정 안 된 담당자·운영 기준은 미확정과 연결 이슈 | 담당자·백업 담당자 지정, 중단/재개 기준, 운영 주기·실제 rollout 의사결정 | +| T01 #615 / T02 #616 | 정상 main mobile setup 재현과 physical/native 계약 검증 차이를 명시 | 전체 E2E suite, 실제 native/물리 기기 검증 | +| R02 #617 / R04 / R09 | 구현 시 실제 workflow·artifact identity·network-boundary 증거에 정확히 연결 | CI 플랫폼 확장, 품질/배포 연결, 최종 binary network 증명. 문서만으로 완료 처리하지 않음 | +| R01/R05–R08 | 실제 구현된 branch/signing/version/release 동작과 대기 정책을 구별 | 저장소 보호·workflow/runtime·버전·signing·release 운영 변경 | + +## 파일별 작업 inventory + +구현 첫 단계에서 repository의 모든 Markdown 목록을 고정 SHA로 추출하여 `path / classification / current entry point / owner issue / replacement / checked source / action / result` 표를 만든다. 아래는 직접 확인한 최소 목록이다. 같은 파일 안에 current 절과 dated evidence가 공존하면 절별로 분류한다. 파일을 읽지 않고 키워드 유무만으로 분류 완료하지 않는다. + +| 파일 또는 명시적 파일 집합 | 분류와 조치 | +|---|---| +| `README.md` | current: 모바일 setup·제품 범위·toolchain·명령·troubleshooting·release 링크를 실제 소스로 재작성. Firebase/API 필수 설정과 product Web 배포 절 제거/역사 연결. | +| `AGENTS.md`, `.github/copilot-instructions.md` | current: actual module/지원 플랫폼/개발 실행 지침을 정합화. generated 금지 정책 유지. 새 요구 권한이나 임의 product policy 추가 금지. | +| `docs/Home.md`, `docs/Wiki-Management.md` | current index: 현행/역사/대기 문서를 구분하고 backend API “Coming Soon”을 현행 제품 계획처럼 소개하지 않음. Wiki publish 절차를 검토하되 실제 push/force push 수행하지 않음. | +| `docs/Architecture.md`, `docs/Error-Handling-Result-System.md`, `docs/Logging-Policy.md` | current: 없는 remote/auth/Crashlytics 경로·예시 수정, 실제 local flow/실패 경계 연결. 긴 일반 예시도 현행 구현인지 교육용인지 명시. O03 진단은 대기 상태. | +| `docs/Git.md`, `docs/Release-Checklist.md` | current: runtime ENV/API 설정 제거, generated 지침 정합화, 실제 workflow와 정책 목표 구분. branch 이름만으로 배포 성공 주장하지 않음. | +| `docs/Android-Release-Configuration.md`, `docs/iOS-Release-Configuration.md` | current: source·lock·toolchain·서명 입력 이름과 주입 방식·명령·검증 범위를 actual workflow/native config와 맞춤. iOS 문서가 이미 local-only라는 사실을 보존. secret 값 기록 금지. | +| `docs/Android-Release-Smoke-Test.md`, `docs/Android-Alarm-Notification-QA.md` | current runbook + dated evidence: login/endpoint/token 전제 제거, Local Profile/offline CRUD/relaunch/notification/backup/reset 경로와 blocked 상태 기록. 과거 QA 결과 재작성 금지. | +| `docs/Android-Manifest-Permissions.md`, `docs/Android-Play-Signing-Fingerprints.md` | current guidance + historical evidence: 과거 merge/인증 fingerprint 기록과 현재 release 검사를 구분. 현재 cert identity 추적은 유지하고 Firebase/auth 필수 등록 전제 제거. 새 merged/packaged manifest 검증 없이 새 결과 통과 표기 금지. | +| `docs/Release-Rollout-Monitoring.md` | current + pending O03/O05: 제거된 서비스 관측을 현행 필수로 요구하지 않음. 가능한 OS/store/수동 지원 증거의 확인 여부를 명시하고 자동 수집/새 운영 담당자를 지어내지 않음. | +| `docs/Play-Pre-Launch-Report.md`, `docs/Play-Review-Rejection-Playbook.md`, `docs/Google-Play-Release-Screenshots.md` | current procedure: 로그인/Firebase/API 환경 요구를 현행 검증 흐름으로 교체. 기존 store 결과·실제 screenshot 날짜는 보존. 실제 콘솔/제출은 O02·release 이슈. | +| `docs/Preparation-Form-Structure.md`, `docs/Schedule-Preparation-SRD.md`, `docs/Schedule-Timer-System.md`, `docs/Schedule-Notification-Flow.md` | current requirements + dated decisions: backend persistence/tie-break 전제를 실제 use case와 구분. timer/알림 정책은 A06/A07 실제 계약을 참조하며 새 동작을 결정하지 않음. | +| `docs/Notification-Behavior.md` | historical 2026-03-21 server/FCM behavior: 현재라는 표현을 명확한 범위 배너로 정정하고 현행 notification flow/verification으로 연결. 당시 원문·증거 보존. | +| `docs/Analytics-Event-Catalog.md`, `docs/Analytics-Preference-API.md`, `docs/Backend-Account-Deletion-Retention-Report.md` | historical/superseded: 이전 서버 제품 자료, ADR-0011 대체와 적용 범위 표시. 원문·과거 issue 증거 보존. | +| `docs/Google-Play-Data-Safety.md`, `docs/Privacy-Policy-Draft.md`, `docs/Privacy-Policy-Hosting.md`, `docs/Account-Deletion-Request-URL.md` | pending-other-issue O02: 이전 모델/현재 제출 근거 아님/검증 미완료 배너와 O02 연결만. 과거 체크박스가 새 local-only 신고 완료로 읽히지 않게 함. 호스팅 주소의 현재 유효성을 추정하지 않음. | +| `docs/Google-Play-Listing-Copy.md`, `docs/Google-Play-Listing-Graphics.md` | pending O02/release: source 문구와 실제 공개 상태를 구분하고 인덱스의 현재성 확인. 실제 외부 콘텐츠 수정은 별도 범위. | +| `docs/Android-Developer-Verification.md`, `docs/Android-Release-Signing.md`, `docs/Android-Signing-Setup.md`, `docs/Google-Play-Full-Screen-Intent-Declaration.md` | current procedure 또는 dated remediation: 모든 링크/명령/정책 상태를 확인하되 단순 키워드 일괄 치환 금지. full-screen 제거/금지 결정을 다시 활성화하지 않음. | +| `docs/Dependency-Refresh-394.md`, `docs/App-Improvement-Audit-2026-09-23.md`, `docs/Mobile-Backup-Verification.md`, `docs/Runtime-Privacy-Verification.md`, `docs/Android-Notification-Delivery-Verification.md` | dated-evidence: 당시 SHA/검증 범위·미실행을 보존. 새 결과는 날짜/SHA가 구별된 링크나 별도 항목으로 추가. 과거 test count를 최신 수치로 덮어쓰지 않음. | +| `docs/adr/*.md`, `CONTEXT.md` | policy/glossary: accepted/superseded 관계와 current 문서 링크 검토. 기존 superseded 상태를 존중. CONTEXT 중복 정의는 별도 정리 대상으로 추적하고 O01 구현 메모를 넣지 않음. | +| `docs/design/**`, `plans/**`, `handoff/**`, `.github/ISSUE_TEMPLATE/**`, `.github/pull_request_template.md`, 기타 tracked Markdown | snapshot/plan/template별 분류: 과거 계획·화면·대화·검증 결과를 현재 제품 사실로 일괄 바꾸지 않음. active template이 제거된 설정을 요구하면 current 문구만 정정. 다른 이슈의 원문 문답은 보존. | + +## 구현 단계 + +1. 고정 조사 SHA와 구현 시작 SHA를 기록하고 `git ls-files '*.md'` 기반 inventory를 생성한다. 현재 링크 그래프에서 README→setup→architecture→release→smoke로 실제 독자가 이동하는 경로를 먼저 검토한다. +2. 실제 `pubspec.yaml`/lockfiles, `lib/main.dart`, startup/database/backup/workflow 코드, native manifest/project, `.github/workflows/`, 기존 `tool/` 검사를 대조한다. 정책 목표와 현재 구현이 다르면 owner issue를 연결한다. +3. current 문서를 고치고 historical/pending/dated 표지를 추가한다. 경로를 유지하고 모든 대체 링크가 실제 존재하는지 확인한다. 소스 코드 예시를 실제 코드처럼 소개한다면 해당 SHA에서 존재하고 의미가 일치해야 한다. 교육용 예시는 그 사실을 명시한다. +4. 다음 정책을 일관되게 반영한다. + - Local Profile은 로그인 계정이 아니다. `AuthBloc` 같은 잔존 내부 타입 이름을 remote authentication 존재의 근거로 사용하지 않는다. + - encrypted durable DB는 source of truth이고 device-bound key와 Backup Password는 다르다. 복원은 검증된 backup으로 교체이며 실행 중 timer/OS registry를 백업 데이터로 소개하지 않는다. + - Android notification과 iOS AlarmKit/fallback을 구분한다. A06의 실제 paused foreground-process periodic delivery를 suspend/kill 후 보장으로 확대하지 않는다. + - OS file provider 및 사용자가 명시적으로 여는 외부 앱은 제품 자동 네트워크/API가 아니다. bundled legal information과 외부 링크의 차이를 설명한다. + - debug transport/Widgetbook Hosting과 product release boundary를 구분한다. +5. 기존 generated checker를 재사용하고 가벼운 docs 검사를 추가/연결한다. current Markdown의 local file·anchor와 현재 소스 인용·명령 파일 존재를 검사하되 임의 코드블록 실행을 하지 않는다. 파일명 대소문자·경로 URL 인코딩·헤딩 중복 anchor·historical 링크 예외를 명시한다. 날짜 증거의 옛 경로는 명시적 historical 문맥/고정 commit 링크로 처리한다. +6. 깨끗한 전용 checkout에서 README 명령 순서대로 재현한다. sdk/package cache 재사용 여부와 tracked/untracked 시작 상태, source SHA, toolchain/lock hash를 기록한다. 정상 ignored 생성은 허용하되 tracked source/lock 변경은 검토 없이 성공 처리하지 않는다. +7. 새 소유 Android emulator/iOS simulator에서 기본 `lib/main.dart`로 onboarding→Local Profile→합성 일정 생성→실제 프로세스 종료/다른 PID 재실행→일정 값 보존을 확인한다. QA 전용 entrypoint/fixture 화면/Widgetbook 렌더는 이 검증의 대체가 아니다. +8. 적용된 문서와 검증 근거·미실행을 review하고 이슈 본문/PR에 exact SHA·로그·증거 링크를 연결한다. 완료 조건을 충족하지 못하면 막힌 행과 담당 이슈를 남긴다. + +## 명령과 검증 기록 + +최종 README에는 실제 실행한 순서와 Android/iOS별 전제 조건을 기재한다. 아래는 구현 시 검증할 명령 집합이며 이 이슈 작성에서 실행 완료한 명령이 아니다. Flutter SDK 버전 3.44.4/Dart 3.12.2를 기록하고 `pubspec.yaml`의 Dart 최소 제약과 동일 개념으로 서술하지 않는다. + +```sh +flutter --version +dart --version +flutter doctor -v +flutter pub get +dart run build_runner build --delete-conflicting-outputs +dart run tool/check_generated_dart_policy.dart +dart run tool/check_local_only_boundary.dart +flutter analyze +flutter test --coverage +dart run tool/check_coverage.dart --min=80 +flutter devices +flutter run -d +flutter run -d +git diff --check +git status --short +``` + +`<...>`는 사용자가 실제 나열된 device ID로 치환할 값임을 설명한다. 첫 install 시 네트워크가 필요한 toolchain/package fetch와 앱 runtime network-free 계약은 다르다. pub get 후 lock diff가 있다면 locked 재현 성공으로 처리하지 않는다. 같은 source·같은 명령·환경의 검증 가능한 CI 결과를 재사용할 때는 run/attempt, 실제 checkout SHA, 결과/로그를 연결한다. 오래된 run 이름이나 다른 synthetic merge의 성공을 현재 성공으로 사용하지 않는다. + +서명 release APK/AAB/IPA 명령은 필요한 signing 입력 이름과 안전한 주입 경로만 설명한다. debug emulator/simulator 재현은 서명 release/실제 물리 기기/Play App Signing/스토어 처리 증거가 아니다. O01을 이유로 실제 signing/upload/rollout 또는 wiki publish를 실행하지 않는다. Widgetbook은 별도 디렉터리 dependency/codegen/build/run 순서로 검증하고, 확인하지 못한 경로는 미검증으로 표시한다. 코드베이스의 실제 플랫폼 최소 버전은 native 설정에서 확인하며 오래된 commented Podfile 값을 제품 최소 지원의 근거로 쓰지 않는다. + +## 검증 행렬 + +모든 실행 행은 현재 계획/미실행이다. O01 구현 시 결과·정확한 증거·blocked 사유를 채운다. + +| ID | 검증 | 통과 기준 / 실패로 구분할 조건 | +|---|---|---| +| D01 | 전체 Markdown inventory | 모든 tracked Markdown에 current/historical/pending-other-issue/dated-evidence 또는 명시한 glossary/policy/template 하위 역할, 책임·대체 링크가 있음. 읽지 않은 문서를 완료로 분류하지 않음 | +| D02 | 현행 진입 경로 | README/Home/Architecture/release/smoke/agent 지침이 서버·로그인·FCM·Firebase 제품 설정을 요구하지 않음. Widgetbook/금지 규칙/과거 문맥은 허용 | +| D03 | current local references | 내부 파일 링크·헤딩 anchor·소스 경로·명령 script가 존재하고 대소문자까지 일치. historical 부재 경로는 고정 commit 또는 명시된 역사 예외로 구분 | +| D04 | generated 정책 | 기존 checker 통과, ignored g/config/freezed/mocks를 staged/tracked하지 않음. tracked l10n·ARB·native source·lock 검토 누락을 ‘모든 generated 금지’로 정당화하지 않음 | +| D05 | clean checkout toolchain/deps | SHA/SDK/lock hash/시작 상태 기록. 문서 순서로 pub get/codegen 성공하고 예상하지 않은 source/lock drift 없음 | +| D06 | 실제 품질 명령 | analyze/test/coverage/local-only/generated 검사 모두 실제 결과 또는 동일 identity의 유효 CI 증거. 없는 검사 명령·placeholder green 금지 | +| D07 | Android 문서 재현 | 새 소유 emulator에서 정상 main·onboarding·일정 저장·실제 PID 변경 relaunch 후 값 보존. APK compile만으로 통과 금지 | +| D08 | iOS 문서 재현 | 새 소유 simulator에서 같은 여정 관찰. Swift parse/typecheck/unsigned build만으로 통과 금지 | +| D09 | 보존/권한 경계 | 기존 simulator·실기기·사용자 데이터·키체인을 지우지 않음. 설치/삭제는 전용 새 target 소유가 증명된 범위 | +| D10 | Widgetbook 구분 | catalog가 제품 Web support로 소개되지 않음. catalog 명령 성공은 제품 data/native 기능 proof가 아님. 실행 안 한 경로는 미검증 | +| D11 | 과거 증거 보존 | 과거 날짜·SHA·실행 범위·실패/미실행을 그대로 유지. ADR 0001 등 기존 superseded 상태와 현행 대체 링크 정합 | +| D12 | O02/O03/O05 분리 | 문서 배너/링크가 법적 제출·진단 구현·운영 담당자 확정을 대신하지 않음. 관련 기능/콘솔 검증 대기가 명확 | +| D13 | docs checker 음성 시나리오 | 임시 fixture의 깨진 current local link/anchor·없는 현재 명령 파일은 탐지. historical 고정 SHA 경로·Widgetbook Firebase·금지 규칙 단어를 허위 오류로 처리하지 않음. 실제 repository 파일을 망가뜨리지 않음 | +| D14 | 외부 링크/게시 | 필요한 공식 참조만 정확 확인, 결과/날짜 구별. 외부 링크 대량 scan·비밀값 노출·자동 wiki/store publish 없음 | +| D15 | 신선한 최종 검토 | 조사 SHA와 구현 SHA 차이를 확인하고 A08 등 병행 WIP를 current 완료로 섞지 않음. 최종 추적 문서/issue body read-back 일치 | + +문서 자체가 새로운 기능을 구현하는 변화는 아니므로 동작을 그대로 모사하는 Flutter 테스트를 추가하지 않는다. docs 검사 회귀는 링크/anchor 등 실제 실패 조건에 집중한다. 제품 실행 확인과 기존 suite는 환경·문서 명령 재현성을 검증하기 위한 것이다. + +## 완료 조건 + +- [ ] 전체 파일 inventory 및 위 주요 경로의 현행/과거/대기/증거 분류가 작성되고 검토됐다. +- [ ] 현재 개발·출시 안내에서 삭제된 제품 기능과 잘못된 required config가 제거됐고 실제 source/policy/대기 작업의 차이가 드러난다. +- [ ] AGENTS/Copilot/README/Release의 generated 정책이 일치하며 추적 l10n/ARB/native/lock 예외를 정확히 구별했다. +- [ ] current 내부 링크·anchor·source/command 경로 검사와 의미 있는 docs 음성 시나리오가 통과했다. +- [ ] 깨끗한 고정 checkout의 문서 명령 재현 및 동일 identity의 analyzer/test/coverage/policy 증거가 기록됐다. +- [ ] Android 새 emulator와 iOS 새 simulator 양쪽에서 정상 main→onboarding→일정 저장→실제 PID 재실행 보존을 관찰했다. SDK/runner가 없거나 한 플랫폼만 완료면 이 조건은 미완료다. +- [ ] release signing/physical QA/store/read-back와 문서 debug 실행을 분리했고 T01/T02/R02/R04/R09를 문서만으로 완료 처리하지 않았다. +- [ ] O02/O03/O05 책임·pending 링크를 제공하고 현재 없는 진단/운영/개인정보 신고를 완료라고 쓰지 않았다. +- [ ] 과거 문답·QA/ADR 원문과 적용 시점은 보존했으며 외부 wiki/store 게시를 수행하지 않았다. +- [ ] final source SHA, 문서 diff, 실행 로그/exit status, 모바일 OS/device/PID/화면·합성 fixture 값, 실패·미실행, 변경된 문서 목록과 실제 GitHub issue read-back을 연결했다. + +## grill-with-docs 전체 문답 + +전담 서브에이전트 `/root/grill_o01`이 `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`를 읽고 고정 소스를 탐색한 뒤 한 번에 한 질문을 보냈다. 사용자가 위임한 대로 root가 대신 답했다. 아래는 실제 질문과 답변이며 이 문서에서 새로 만들어낸 사용자 승인 기록이 아니다. 기존 정책/용어를 재사용하므로 신규 ADR이나 CONTEXT 구현 메모는 만들지 않는다. + +### 질문 1 — 현재·역사·다른 이슈의 문서 책임 + +> O01 질문 1 — 현재 문서·과거 기록·다른 이슈 소유 문서를 어떤 방식으로 정리할까요? 권고: README/Architecture/개발 지침/현행 release·smoke 경로는 실제 local-only 동작으로 고치고, 폐기된 Analytics API/이벤트·기존 서버 삭제 보고·ADR 0001–0007 등은 원문과 과거 증거를 보존한 채 맨 위에 historical/superseded, 적용했던 시점·대체 ADR/현행 문서 링크를 둡니다. O02 개인정보·store worksheet는 O01에서 ‘이전 서버 모델, 현재 제출 근거 아님, O02 검증 전 미완료’ 표지만 붙이고 실제 법적 문구/콘솔 완료는 O02에 남깁니다. O03 진단 구현·O05 담당자/중단 기준은 링크와 미확정 상태만 표시하되 현행 문서의 Firebase/백엔드 운영 전제는 제거합니다. 파일 이동보다 기존 경로 보존을 기본으로 하여 과거 링크를 깨지 않게 합니다. 근거: README는 없어진 environment_variable.dart/Firebase 파일과 REST_API_URL을 필수로 지시하며, Release-Checklist는 local-only 선언과 Firebase/sign-in 요구가 한 파일 안에서 충돌합니다. 이 경계로 확정할까요? + +**root 답변:** + +> Q1 채택합니다. README/Architecture/개발지침/현재 release·smoke 안내는 검증된 local-only 실제동작으로 고치고, 역사 Analytics/API/서버삭제보고/과거ADR 원문·증거는 보존하면서 historical/superseded·적용시점·현행대체링크를 명시. 기존경로 보존 기본, 삭제/이동보다 현재 진입점에서 명확히 구분하세요. 과거ADR에는 기존superseded 상태가 있으면 중복표지 대신 정확한 연결 정비. O02 문서에는 이전서버모델/현재제출근거아님/검증미완료 상태만, 실제법적문구·콘솔변경은 해당이슈. O03/O05 실제진단/운영책임 미확정은 링크/상태만 남기고 현재문서의 Firebase/backend 전제 제거. 실제 현재코드가 미구현인 정책은 '정책상 목표/구현대기'로 분리해 문서만 바꿔 완료처럼 만들지 않습니다. 고정 조사시점 근거와 구현시 fresh source/CI 재검증을 구별, schema3 A08WIP를 현재출시사실로 쓰지 않습니다. + +질문 이후 소스 확인 보충: ADR-0001과 ADR-0006에는 이미 `superseded by ADR-0011` 상태가 있다. 따라서 원래 상태를 덮어쓰거나 무조건 배너를 중복하지 않는다는 root 정정을 적용한다. + +### 질문 2 — 두 모바일에서 새 문서의 재현을 무엇으로 증명할지 + +> O01 질문 2 — ‘새 문서만 따라 모바일 실행 가능’의 완료 증거를 어디까지 요구할까요? 권고: 구현 시 고정한 새 커밋의 깨끗한 별도 checkout에서 README 순서대로 Flutter 3.44.4/Dart 3.12.2·locked dependencies·codegen·generated policy·local-only boundary·analyze/test/coverage를 실행하고, 새 전용 Android emulator와 iOS simulator 양쪽에서 기본 main 진입→onboarding/Local Profile→일정 저장→프로세스 재실행 보존을 실제 관찰합니다. 체크리스트의 release APK/AAB/IPA는 서명 요건/명령을 정확히 기술하되 문서검증 debug 실행과 release/실기기/스토어 검증을 구별하고 T01/T02/R02/R04/R09 결과로 연결합니다. 같은 source·같은 명령의 유효한 CI proof는 중복 실행 대신 연결할 수 있지만 두 모바일 문서 경로 실행은 compile 결과로 대체하지 않습니다. 기기/SDK 부재면 해당 O01 행도 blocked로 남겨 전체 완료라 하지 않습니다. Web/desktop은 제품 지원 아님; Widgetbook만 별도 개발 catalog·Hosting으로 분리하고 실행되지 않은 도구 경로는 미검증 표시합니다. 기존 QA simulator/사용자 앱 데이터는 건드리지 않습니다. 이 완료 경계를 채택할까요? + +**root 답변:** + +> Q2 채택합니다. 새 문서가 가리키는 고정 commit의 깨끗한 별도 checkout에서 문서순서대로 locked toolchain/deps/codegen/정책/analyze/tests/coverage를 실행하고, 두 모바일은 소유 새 emulator/simulator의 정상main→onboarding/localprofile→일정저장→실제PID재실행 보존으로 안내의 재현성을 확인. 동일source·명령·환경의 유효CI 증거는 재사용하되 문서의 OS실행 단계는 compile로 대체금지. debug 문서재현/서명release build/물리QA/스토어를 구별, 서명secret 요구는 이름/주입방식만 문서화하고 비밀값 포함금지. 제품지원 Android/iOS, Web/desktop 프로젝트 흔적≠지원제품, Widgetbook catalog/hosting은 분리하며 미검증 실행은 표시. 기존target/data보존, 해당SDK/runner없으면 그행미실행과 전체O01완료를 구별. 단순 문서링크수정만으로 T01/T02/R02/R04/R09완료 주장 금지, 관련실행증거를 정확히 교차연결하세요. + +### 질문 3 — 문서 역할·회귀 검사·범위 확정 + +> O01 질문 3 — 이후 낡은 설명이 다시 유입되는 것을 어떤 문서 계약으로 막을까요? 권고: repo README=개발 진입점, docs/Architecture=현재 source 구조, 현행 Release Checklist=실행 요구와 아직 미검증 항목, CONTEXT=도메인 용어, ADR=정책 결정, 날짜 있는 QA 자료=그때의 증거로 역할을 고정합니다. 이번 구현 산출물에 모든 docs Markdown/AGENTS/Copilot/README의 파일별 current·historical·pending-other-issue·dated-evidence 분류와 현행 대체 링크를 기록하고, current 문서 내부 링크/앵커·인용 소스 경로·명령 파일 존재를 검사합니다. rg의 서버/Firebase/login 키워드는 검토 후보 탐지용일 뿐 일괄 금지하지 않습니다(Widgetbook Hosting, 금지 규칙, 과거 ADR, legacy cleanup은 합법 문맥). generated 정책은 기존 check_generated_dart_policy.dart를 단일 검사로 재사용하고 ignored g/config/freezed/mocks와 ARB에서 생성되지만 추적되는 l10n 출력/플랫폼 소스·lockfiles를 구별합니다. 가벼운 repo docs 검사에 진입점 현재성·깨진 내부 링크를 연결하되 제품 Flutter 테스트와 허위로 합치거나 새 대형 문서 시스템을 만들지 않습니다. 새 미래기능 정책/서명·배포 실행/위키 push는 O01 범위에 넣지 않고, drift 발견은 연결 이슈와 미완료 증거로 남깁니다. 이 기준으로 상세 이슈를 확정해도 될까요? + +**root 답변:** + +> Q3 채택합니다. README개발진입/Architecture실제구조/ReleaseChecklist실행요구·미검증/CONTEXT용어/ADR정책/날짜QA당시증거 역할을 구별. repo markdown(AGENTS·Copilot포함) inventory에서 current/historical/pending-other-issue/dated-evidence 분류·대체링크·책임을 명시하고 current내부링크/앵커/소스경로/명령파일 존재 검증. keyword는 review후보일뿐 blanketban 금지, Widgetbook Hosting·정책금지·과거/legacy cleanup의 정당문맥 허용. existing generated checker 재사용, ignored g/config/freezed/mocks와 tracked ARB/l10n/platform/lock 구별. 가벼운 docs check로 내부 참조회귀를 막되 임의 빌드/환경명령 실행이나 외부 링크 대량 networkscan은 피하고 필요한 official링크만 정확확인. 새로운 대형 docs framework/제품정책/서명배포/위키publish는 범위밖. 불일치 코드가 있는 정책을 문서로 숨기지말고 연결issue+미완료로 기록. 전체문답·구체파일inventory·검증행렬·AC와 경계로 동결해주세요. + +## 문서 결정의 성격 + +이번 세 문답은 기존 local-only 정책을 문서와 실행 증거에 정확히 반영하는 책임·검증 범위를 정했다. 새 동기화/로그인/analytics/원격 진단 도입, 데이터 파괴, store/위키 게시, release 승인에 대한 별도 권한을 부여하지 않는다. 조사에서 `CONTEXT.md`의 기존 용어 중복을 드러냈지만 새 도메인 결정을 하지 않았으므로 신규 ADR 또는 용어집 변경을 요청하지 않는다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 658f982a..77853ef9 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -497,7 +497,7 @@ "`create_schedule_form_submission_use_case.dart:16-23`; `update_schedule_form_submission_use_case.dart:28-35`; `create_schedule_with_place_use_case.dart:17-20`; `schedule_mutation_alarm_effects_coordinator.dart:41-52`", "일정·장소·준비·revision을 하나의 원자적 작업으로 commit 후 알림 effects 실행. 두 번째 write 실패 시 전체 rollback, 같은 입력 재시도 때 중복 일정 없음. 경쟁의 실제 발생은 별도 재현." ], - "status": "ready", + "status": "implementing", "agent": "/root/grill_a08", "issue_number": 601, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/601", @@ -508,7 +508,9 @@ "blockers": [], "sequence": 15, "readback_verified": true, - "body_sha256": "bd5f1b77a400c56f5fb1656412201f72d8288a0a63ed79a699a16f45e7cc1c76" + "body_sha256": "bd5f1b77a400c56f5fb1656412201f72d8288a0a63ed79a699a16f45e7cc1c76", + "implementation_agent": "/root/implement_a08", + "implementation_plan": "plans/audit-2026-09-23/a08-implementation-preflight.md" }, { "audit_id": "A09", @@ -940,16 +942,18 @@ "README와 Architecture/Smoke Test/Monitoring 문서가 제거된 서버·로그인·FCM·Firebase를 현재 기능처럼 설명한다. Release-Checklist는 generated Dart를 commit하라는 문장도 남아 AGENTS와 충돌한다.", "`README.md:5,68-129`, `docs/Architecture.md:134-138,298`, `docs/Android-Release-Smoke-Test.md:57-60`, `docs/Release-Checklist.md:77-78` 정리. 깨끗한 checkout에서 새 문서만 따라 모바일 실행 가능. 과거 자료는 historical/superseded 표시." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_o01", + "issue_number": 621, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/621", "decisions_file": "plans/audit-2026-09-23/issues/O01.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 34 + "sequence": 34, + "readback_verified": true, + "issue_body_sha256": "b044e206f9456258364168e13d501463bf7584bb655766bdffb8ed88ffa3fd81" }, { "audit_id": "O02", From f5a41ccfe57f58f4cff40df3f635fbe9cf84a284 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 12:28:22 +0900 Subject: [PATCH 52/59] docs: define privacy and store disclosure audit scope --- handoff/audit-2026-09-23-progress.md | 2 + plans/audit-2026-09-23/README.md | 3 +- plans/audit-2026-09-23/issues/O02.md | 204 +++++++++++++++++++++++++++ plans/audit-2026-09-23/state.json | 12 +- 4 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/O02.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 711f4cf4..b2180993 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -136,3 +136,5 @@ R09#620 상세 이슈 게시·원문 readback 완료,34/64(미생성30). O01 /ro A08 진행 메모: root는 schema3/store+aggregate identity 외에 안정된 recurring rootSegmentId, template/definition/default 의존 version, 정확 정수분 input digest, create 보호 metadata 거부, pending delivery 이후 currentness, 같은Bloc 다른form callback ownership, Drift trigger의 watch invalidation을 추가 리뷰했다. 실제 전체 문답은 구현 담당이 /tmp/a08-review-transcript.md에 취합 중이다. 최초 negative test는 준비 INSERT 실패 후 schedule row 잔존을 실제 관찰(/tmp/a08-negative.log), 현재 소스/테스트/ARB는 WIP이며 커밋 금지, final sourcefreeze/full validation 아직 없음. O01#621 실제3문답·파일inventory/모바일실행·15개검증 기준 게시/readback완료,35/64(미생성29). O02 전담 /root/grill_o02 시작. A08 초기 analyzer compile error0, 경고14 정리 중; historical schema2는 baseline4529 실제DB DDL 추출, schema1 ccc6272와 차이 확인. 집중/전체 최종검증 아직 미완료. source freeze 전 WIP는 구현agent 소유, root Flutter금지. 마지막 검증된 원격 제품은 C01 aa2fda68이며 현재 로컬 docs HEAD9682c34e 이후 관리문서 추가 중. + +O02#622 실제3문답·데이터흐름11·검증22개 기준 게시/readback완료,36/64(미생성28). 공개URL/스토어 현재상태는 미조회 unknown이며 외부정책 변경 없음. 새 D06 전담 /root/grill_d06 문답 착수. A08 중간 focused52/migration5 통과, 새초안 stale saveReceipt 경계와 watch rollback 검증 보완 중. Q4 내부 recurring create의 조용한 중복성공을 typed conflict로 교체하고 미사용 save 우회 경로 제거 채택; 제품 중복성공은 aggregate durable receipt 경계로 한정. 최종 sourcefreeze/전체검증 전 제품 WIP 커밋 금지. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index ecaf7d27..470ea2b1 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -51,8 +51,9 @@ | R01 | [#619](https://github.com/DevKor-github/OnTime-front/issues/619) | 실제 3문답·비활성 ruleset 재사용/최신 소스 검사·독립 리뷰·15개 검증 행렬 확정 | | R09 | [#620](https://github.com/DevKor-github/OnTime-front/issues/620) | 실제 3문답·최종 APK/IPA 구성/오프라인/트래픽 귀속·20개 검증 행렬 확정 | | O01 | [#621](https://github.com/DevKor-github/OnTime-front/issues/621) | 실제 3문답·현행/역사 문서 inventory·두 모바일 재현·15개 검증 행렬 확정 | +| O02 | [#622](https://github.com/DevKor-github/OnTime-front/issues/622) | 최종 바이너리와 실제 배포 범위에 맞춘 개인정보 정책·스토어 신고 및 공개 검증 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **35/64개**를 게시하고 원격 본문 동일성을 확인했으며 **29개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했고 C01 구현을 시작했다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **36/64개**를 게시하고 원격 본문 동일성을 확인했으며 **28개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했다. C01은4529d9f8로 구현해 최종 원격959 tests·88.72%·APK를 통과했고 A08 구현 중이다. A08의 현재 부분 검증은 집중52개와 역사 migration5개이며 최종 전체 검증은 남아 있다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/O02.md b/plans/audit-2026-09-23/issues/O02.md new file mode 100644 index 00000000..d23b242c --- /dev/null +++ b/plans/audit-2026-09-23/issues/O02.md @@ -0,0 +1,204 @@ +# [O02][P1] local-only 실제 바이너리·배포 범위에 맞춰 개인정보 정책과 양 스토어 신고를 검증하고 원격 적용 증거 연결 + +## 문제와 목표 + +앱 안에서는 서버 계정과 외부 전송이 없는 local-only 제품을 설명하지만, 개인정보·Data Safety 문서는 계정 인증, FCM, 서버 API, 원격 analytics를 현행 동작처럼 설명한다. 이전에 Play Console에 저장했다는 문서는 2026-05-10 기록이며 현재 콘솔 또는 공개 페이지를 이번 조사에서 읽은 증거가 아니다. 새 RC에 Firebase가 없다는 소스 사실만으로 package 전체 신고를 ‘수집 없음’으로 바꾸면, 아직 배포 중인 이전 버전의 관행을 빠뜨릴 수 있다. + +O02는 **최종 실제 제품의 데이터 흐름, 스토어 신고 적용 범위, 앱 번들·공개 개인정보 정책, 실제 저장·제출·공개 상태**를 연결한다. 확인되지 않은 법적 사실이나 기존 사용자 데이터 삭제를 선언하지 않는다. 저장소 정비가 끝나도 외부 검증·적용이 남으면 O02 전체는 OPEN이다. + +- 원 감사: `docs/App-Improvement-Audit-2026-09-23.md:142`, 64개 항목 중 O02. +- 우선순위 P1. 개인정보 설명과 출시 신고의 신뢰성에 직접 영향을 주며 해당 출시 전 미확인 사항을 해소해야 한다. +- 고정 조사 소스: [`aa2fda68fbf3ac431738de3792f5d587d35b6dda`](https://github.com/DevKor-github/OnTime-front/tree/aa2fda68fbf3ac431738de3792f5d587d35b6dda). 조사 중 A08 작업 파일은 근거로 쓰지 않았다. +- 조사일: 2026-09-24. 아래 공식 정책은 이 날짜에 조회했으며 실제 제출 시 다시 확인한다. +- 전담 설계 agent: `/root/grill_o02`. `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`를 읽고 질문을 하나씩 root에게 보냈으며 실제 세 질문과 답변을 하단에 보존했다. root는 제품 판단을 대리했지만 운영주체·보존기간·법적 사실을 만들어 승인하지 않았다. +- 이번 작업은 이슈 설계다. 앱 소스·공유 문서·스토어·호스팅·실서비스·사용자 데이터·Git 설정은 수정하지 않았고 Flutter를 실행하지 않았다. + +## 고정 소스에서 확인한 불일치와 미확인 범위 + +| 표면 | 실제 확인한 내용 | 의미와 필요한 후속 증거 | +| --- | --- | --- | +| `docs/Google-Play-Data-Safety.md:1–29,64–109` | 2026-05-10 saved 상태, 이메일/이름/사용자 ID/진단/상호작용/FCM, OAuth·비밀번호 계정 생성, 계정삭제 URL을 기재 | 과거 답변 원문은 보존. 현재 Play 답변·현재 배포 중인 버전·실제 공개 label은 unknown | +| `docs/Privacy-Policy-Draft.md` | hosted draft, 제품/법적 승인 pending. 계정·소셜 로그인·FCM·서버 처리·analytics 예정, 삭제 피드백 1년/로그 90일/백업 30일을 owner 미확인 상태로 서술 | 미승인 기간을 현행 정책으로 복사하지 않음. 구 서비스 담당자/처리 실재와 현 설치 정책을 구별 | +| `docs/Privacy-Policy-Hosting.md` | `https://ontime-back.duckdns.org/privacy-policy`, 2026-05-10 HTTP/2 200 및 콘솔 입력 완료 기록 | 현재 응답·TLS·redirect·본문·접근성은 이번 조사에서 조회하지 않아 unknown. 파일에 체크가 있어도 현재 live 증거 아님 | +| `docs/Account-Deletion-Request-URL.md` | `https://ontime-back.duckdns.org/account-deletion`, 2026-05-10 complete 표기와 미확인 승인/운영 항목이 함께 존재 | 구 계정의 삭제 요청 경로를 역사 증거로 보존. 새 설치 초기화로 기존 서버 데이터가 삭제됐다고 말할 수 없음 | +| `lib/presentation/my_page/privacy_policy_screen.dart` | 한국어 static 본문, 시행일 2026-08-28, ‘앱 배포 페이지 연락처’만 안내. 번들 본문은 있으나 public policy link 없음 | KO/EN 번들 정책·실제 확인된 문의 수단·문서 revision 정비. Apple 링크 요구와 현 제품 정책의 사용자 명시 외부 위임을 함께 충족 | +| `docs/Google-Play-Listing-Copy.md` | sign-out/account deletion, account controls/계정 관리·일정 알람 표현 | 실제 RC의 Local Profile/Local Data Reset 및 Android Schedule Notification과 일치하도록 KO/EN 교정 | +| `docs/Google-Play-Release-Screenshots.md`, `docs/Google-Play-Listing-Graphics.md` | account controls와 이전 route를 가리키는 캡처 체크리스트/초안 | 실제 RC 캡처·locale·OS·artifact identity로 재검증. 현재 공개 이미지로 추정하지 않음 | +| `pubspec.yaml` | 1.1.0+56은 소스 기본값. Firebase/auth/HTTP SDK 대신 Drift/SQLCipher/secure storage/local notifications/file selector/sodium 등 | 최종 version override·transitive/plugin/native 집합은 서명 artifact의 사실로 재확인. dependency 이름만으로 수집 여부 확정 금지 | +| `lib/data/tables/user_table.dart` | 로컬 ID, note, onboarding·알림·집계·revision/export freshness 필드. 서버 로그인 email/name credential 필드 없음 | 로컬 ID가 존재한다는 것과 서버 계정·수집된 사용자 ID는 다른 판단 | +| `lib/core/database/installation_key_store.dart` | 설치 키 생성·secure storage·iOS device-bound 접근 옵션 | source 의도. 실제 키보관/백업 제외/삭제·잠금 동작 증거는 D03/T02/R09에 연결 | +| `lib/core/database/local_reset_protocol.dart` | 부분 완료·recovery required와 provider 정리 확인을 구별 | 실패/중단 시 ‘전부 삭제 완료’로 표현하지 않는 정책·화면 검증 필요 | +| 추적 iOS source inventory | 고정 commit 트리에 app-owned `PrivacyInfo.xcprivacy` 파일이 발견되지 않음 | embedded SDK privacy manifests/required reason API 판정과 App Privacy label은 다른 문제. 최종 archive/SDK 목록 없이 적합·위반 어느 쪽도 단정하지 않음 | +| 현재 공개 URL·스토어 콘솔·공개 listing·기존 backend 운영 | 이번 설계에서 접근/조회하지 않음 | 전부 unknown. 저장소 문구로 원격 사실을 채우지 않음 | + +## 정책·용어 경계 + +`CONTEXT.md`의 Local Profile, Durable OnTime Data, Local Data Reset, OnTime Backup, User-Initiated External Navigation, Product Network Boundary를 재사용한다. 새 도메인 용어·ADR은 필요하지 않다. ADR 0011/0016/0017/0018/0020/0024/0030/0033의 기존 결정을 유지한다. + +- Local Profile은 로그인 계정이 아니다. Local Data Reset은 현 설치가 소유한 데이터·키·OS 등록·복구 상태를 정리하며 외부로 내보낸 백업, 기존 서버 계정, 수신된 지원 메일을 삭제하는 기능이 아니다. +- 일정·장소·메모에 사용자가 민감한 내용을 입력할 수 있으므로 ‘개인정보를 전혀 다루지 않는다’고 포괄 표현하지 않는다. 기기 안에서의 접근/저장, 스토어 정의상 collection/sharing, 외부 지원 처리를 각각 판단한다. +- 사용자 선택 backup/provider·외부 browser/mail 처리는 앱의 자동 네트워크 요청과 구별한다. 외부 전달이라고 모두 앱 수집도 아니며, OS가 처리한다고 모두 면제도 아니다. 각 경로의 수신자·실제 전달값·현재 스토어 정의를 명시한다. +- 데이터베이스·백업 암호화가 metadata 익명화, 네트워크 전송 암호화 답변, 모든 E2E 예외를 자동으로 충족하지 않는다. key/password·file metadata·header·provider가 실제 볼 수 있는 범위를 각각 확인한다. +- platform backup/transfer 제외는 ADR 0017의 best-effort 요청이다. 모든 OS/OEM 이동을 절대 차단한다는 문구 금지. +- 구 서버형 앱의 서비스·삭제 요청·보존 의무를 신제품 문서 정비만으로 종료하지 않는다. 반대로 과거 설명에 맞추기 위해 서버/auth/analytics를 새 앱에 재도입하지 않는다. +- 새 동의 팝업·추적 SDK·WebView·자동 정책 fetch·network permission을 이 이슈를 이유로 넣지 않는다. + +## 공식 1차 자료와 이슈에 적용하는 범위 + +다음은 기술 검증의 판단 기준이며 포괄 법률 적합성 보증이 아니다. 공식 지침의 조건이 바뀌면 제출 전 답변을 재검토한다. 원문 장문 복제 대신 필요한 적용점만 기록한다. + +| 공식 자료 | 확인한 적용점 | +| --- | --- | +| [Google Play Data safety guidance](https://support.google.com/googleplay/android-developer/answer/10787469?hl=en) | 현재 배포하는 package 전체 관행을 포괄한다. internal-only track 예외와 문서에 명시된 제한적 old-artifact 예외를 임의 확대하지 않는다. on-device 처리, collection, sharing 및 사용자 명시 전송의 조건은 각각 다르며 SDK도 검토한다. | +| [Google Play User Data](https://support.google.com/googleplay/android-developer/answer/10144311?hl=en) | 개인정보 정책은 앱의 처리·보존·삭제·연락 경로를 설명하고 공개 가능한 URL이어야 한다. 앱 내 본문 또는 링크와 콘솔 URL을 검토한다. 수집이 없다는 답변만으로 정책 필요가 사라지지 않는다. | +| [Google Play account deletion](https://support.google.com/googleplay/android-developer/answer/13327111?hl=en) | 계정 생성 제공 여부에 따른 삭제 요구를 판단한다. 기존 계정 서비스의 존재·책임을 확인하지 않은 채 URL 제거 또는 처리 완료로 선언하지 않는다. | +| [Apple App Privacy Details](https://developer.apple.com/app-store/app-privacy-details/) | local-only 처리와 off-device collection을 구별하고, third-party·지원 처리·선택적 공개의 조건을 개별 검토한다. 임의로 ‘optional이므로 제외’하지 않는다. | +| [Apple Manage app privacy](https://developer.apple.com/help/app-store-connect/manage-app-information/manage-app-privacy) | iOS 개인정보 URL과 app-level 답변 적용 범위를 확인한다. 앱 record/platform별 사실을 읽고 가장 포괄적인 실제 처리 범위를 반영한다. | +| [Apple Review Guidelines 5.1.1](https://developer.apple.com/app-store/review/guidelines/#privacy) | metadata와 앱 안에서 쉽게 접근 가능한 정책 링크 및 처리·삭제 설명을 검토한다. 번들 본문만 있다고 URL 요구가 완료됐다고 쓰지 않는다. | + +대상 국가·연령·실제 사업/운영주체·지원 처리 사실이 확인되면 그 조건에 해당하는 공식 법령/개인정보 가이드를 추가 검토한다. 아직 확인하지 않은 관할이나 연령 범위를 만들어 특정 법의 적용/면제를 단정하지 않는다. + +## 구현 범위와 데이터 흐름 inventory + +다음 행을 source 정책, 최종 artifact 관찰, store 적용 범위, 운영주체 사실의 네 근거로 채운다. 단순 yes/no 체크가 아니라 `현재 상태(confirmed/unknown/pending/not applicable) → 판단 이유 → 증거 → 담당 확인`을 기록한다. N/A는 사유와 적용 범위가 있어야 한다. + +각 행의 필수 열: 데이터/메타데이터, 실제 생성·접근 trigger, processor/recipient, 기기 내 저장 위치 종류, 외부 전달값, 목적, retention/deletion, 보호 방식, 사용자 선택, Google collection/sharing 및 Apple collection/linkage/tracking 판단, 예외 적용 조건, source path/commit, 최종 artifact 증거, 현재 배포 범위, unknown/담당자. + +| ID | 경로 | 반드시 분리할 사항 | +| --- | --- | --- | +| F01 | Local Profile·일정·장소·준비/반복 일정·outcome/history·점수·설정·revision | 실제 shipped schema/backup format을 읽는다. 로컬 label의 장소와 GPS 위치 수집을 혼동하지 않는다. WIP schema나 계획한 기능을 출시 사실로 쓰지 않는다. | +| F02 | 설치 키·암호화 DB/부속 파일·잠금·Recovery | 비밀번호/키를 evidence에 내보내지 않는다. 저장·삭제 성공과 잠금/접근실패를 구별한다. | +| F03 | Preparation runtime/action events·알림 registry·ownership journal·reset intent·migration/quarantine 잔여 | durable 데이터와 복구용 상태의 보존/정리 시점이 다름. pending cleanup을 이미 삭제됨으로 쓰지 않는다. | +| F04 | local notification/AlarmKit·권한·잠금화면 내용·OS delivery | 기본 private 내용과 명시 detailed 내용, 플랫폼별 실제 노출 필드. permission state 및 OS 등록과 원격 push/FCM을 구별한다. | +| F05 | 명시 백업/복원·암호화 파일·평문 header/파일명·OS document provider | 저장소/provider 선택, 취소/실패, 파일 전달·외부 동기화 책임, password 비저장·복구 불가, 외부 복사본의 사용자 삭제. cloud 업로드 완료를 앱 성공 receipt로 보증하지 않는다. | +| F06 | platform-managed backup/transfer | OS 설정/packaged 규칙 및 관찰 증거, 제외 요청의 한계. | +| F07 | public legal/store/support 외부 navigation | tap이 없는 경로는 호출 0. 고정 URL만 전달, 프로필/일정/식별자/query/fragment 추적값·첨부 없음. browser가 수행하는 요청과 runtime 요청 분리. | +| F08 | support email·첨부·사용자가 보낸 문의·응답 | 실제 받은 정보/processor/수신자/목적/보존/삭제·연락 책임 확인. 자동 로그 첨부나 지원 전송 기능을 새로 만들지 않는다. | +| F09 | public policy hosting·CDN·접속 로그/cookie·외부 resource | 실제 host가 처리하는 IP/request 정보·서비스 provider·retention 확인. 앱을 열었다는 이유만으로 policy site 접속을 유발하지 않는다. | +| F10 | app diagnostics·OS/store 측 집계·외부 crash/analytics | O03와 R09 source/artifact·SDK 및 owner가 실제 받는 데이터 검토. ‘Firebase 제거’만으로 OS/store/support 전체 처리가 없다는 결론 금지. | +| F11 | 구 서버형 배포·계정·기존 support/deletion URL·retained 자료 | 현재 배포 범위 및 책임·보존 실재를 확인할 때까지 unknown. 기존 자료 정리·실서비스 접속·삭제는 이 설계에서 실행하지 않는다. | + +### 스토어 적용 범위와 신고 worksheet + +1. Play `club.devkor.ontime`의 app identity, 각 현재 track/버전/지역/사용자 대상/rollout 상태, 현재 배포 중인 legacy artifact를 read-only로 확인한다. internal-only 예외는 해당 사실이 확인된 행에만 적용한다. 다른 package로 착각하지 않는다. +2. Apple은 실제 app record ID·bundle ID·플랫폼·배포 버전/현재 제공 상태를 확인한다. repo iOS 폴더가 곧 공개 App Store 앱의 존재라는 뜻은 아니다. 접근/record 확인 실패는 unknown이다. +3. new local-only RC와 전체 신고 범위를 별도 표로 둔다. legacy 배포가 확인되면 old/new 버전별 설명과 global 답변의 집계를 유지하고 담당자가 실제 배포 전환을 판단한다. 이슈를 맞추기 위해 구 artifact 배포를 임의 중단하지 않는다. +4. 각 실제 console 필드에 기존 값/제안 값/근거/영향 버전/확인자/적용 상태를 기록한다. collection/sharing, purpose, required/optional, encryption in transit, deletion, account creation, Apple linked/tracking, policy URL을 실제 보이는 필드 기준으로 매핑한다. collection이 없을 때 나타나지 않는 질문에 임의 Yes/N/A를 만들어 저장하지 않는다. +5. SDK privacy manifest/required reason API 검토는 R09/R02 최종 archive 근거에 연결하고 store label과 동일 개념으로 취급하지 않는다. source dependency 또는 unsigned typecheck만으로 서명 artifact SDK 집합을 증명하지 않는다. + +### 정책·번들 UI·metadata 변경 + +- 기존 privacy/Data Safety/계정삭제 문서의 역사 원문과 날짜·과거 승인 pending은 보존한다. 현행 정책과 과거 증거의 링크를 명확히 하고 새 운영 worksheet를 동일 진입점에서 찾게 한다. O01의 역사 표시와 충돌하지 않도록 합친다. +- 하나의 승인된 policy revision에 KO/EN 본문, 확인된 운영주체/문의 경로, 적용 제품·버전 범위, 실제 시행일을 연결한다. 소스 default 날짜·임의 미래 시행일·이전 연락처를 자동 신뢰하지 않는다. +- 번들 정책은 네트워크 없이 읽히며 접근성/영어 locale을 지원한다. 공개 HTTPS URL이 승인·현재 본문·접근성까지 확인되면 사용자가 눌러야 OS 외부 브라우저로 열리는 policy link를 추가한다. 고정 URL 전달 외 자동 fetch/WebView/network 권한·SDK 추가 없음. URL 미확정은 placeholder 게시 또는 깨진 링크로 완료 처리하지 않는다. +- 문의 수단은 실제 확인된 연락처를 읽고 복사할 수 있게 한다. 외부 mail을 지원하면 기존 명시 OS 위임 경계만 사용하고 앱 데이터·로그·백업을 자동 첨부하지 않는다. 지원을 위해 이름/email 계정 필드를 앱에 추가하지 않는다. +- privacy flow와 연결되는 My Data/Recovery의 partial reset·백업 설명을 실제 receipt와 대조한다. D03/C01/A09 구현을 O02에서 대체하지 않는다. +- KO/EN listing·screenshots·설정 화면에서 로그인/로그아웃/계정 관리/원격 sync/FCM/수집 없음 등의 주장을 실제 지원 기능과 맞춘다. Android notification과 iOS AlarmKit/fallback을 구별하며 모든 플랫폼에서 alarm 보장이라고 홍보하지 않는다. +- 실제 RC에서 synthetic 데이터로 캡처하고 파일 hash·locale·OS·device·artifact identity를 남긴다. Widgetbook/mock/design PNG를 실제 제품 실행 증거로 쓰지 않는다. + +## 증거 identity·외부 적용·보관 + +각 RC의 source commit, lockfiles digest, build/workflow run+attempt, Android package/version/AAB digest·설치 split 집합·signing identity, iOS bundle/version/archive/IPA digest·signing identity를 R04/R08/R09와 연결한다. store가 재서명·가공한 배포물은 원본과 byte-identical이라 하지 않고 store receipt 및 그 변환 관계를 기록한다. SDK/source/설정이 바뀌면 영향을 받는 데이터 흐름 판단을 재검토한다. + +정책 bundle/public HTML revision·digest, KO/EN 번역 대조, 실제 visible text, 변경 전후 field-answer diff, 화면 캡처의 artifact identity를 연결한다. HTML 원본 byte hash와 visible policy text/version은 구별한다. CDN/locale/encoding 차이를 처리한다는 이유로 옛 본문을 동일하다고 정규화하지 않는다. + +공개 게시와 콘솔 저장·제출은 정확한 payload·대상·적용 범위가 검토 가능하고 해당 쓰기 권한이 확인된 다음 실행한다. 이번 설계는 이를 실행하거나 승인하지 않았다. 이후 적용 시에는 다음을 별도로 read-back한다. + +- public URL의 final redirect/status/content type·title·정책 revision/본문·hash, 무로그인 접근, 로그인/다운로드/PDF/geo 제한 여부. 하나의 네트워크에서 HTTP 200만으로 전 지역 접근 가능을 보증하지 않는다. +- Play/Apple의 실제 app identity·field 값 및 saved/draft, submitted, reviewed/accepted, 공개 label/listing 상태. 저장 성공 ≠ 제출, 제출 ≠ 승인, 승인 ≠ 모든 지역의 즉시 공개. +- 공개 앱 설명·KO/EN 스크린샷·policy 링크의 사용자 관찰 결과. CDN 지연이 있으면 실제 옛 본문이 남은 상태를 기록하고 pending으로 둔다. +- 스크린샷은 필요한 app/field/value/status만 포함한다. credentials, private console 계정 정보, 키, 실제 사용자 데이터·메일 내용은 commit/issue/artifact에 넣지 않는다. + +## 실패·검증 행렬 + +아래는 구현 시 실행해야 할 사례이며 현재 모두 미실행이다. fixture 검사는 검증기 행동만 증명한다. 실제 외부 상태·서명 artifact·OS 실행을 대신하지 못한다. + +| ID | 조건/실패 주입 | 기대 결과와 증거 | +| --- | --- | --- | +| P01 | 새 RC는 local-only이나 closed/open/production에 서버형 버전 잔존 | global ‘수집 없음’ 전환 차단. 실제 track/version/region과 version별 처리 표 유지 | +| P02 | console/app record 접근 불가 또는 package/bundle 불일치 | unknown/pending. 다른 app 응답·옛 screenshot으로 채우지 않음 | +| P03 | final artifact에 source inventory 이후 SDK/config/extension 추가 | 영향 flow의 판단 무효화·재검토. source/lock/artifact와 R09 identity 비교 | +| P04 | KO는 새 정책, EN은 구 계정/FCM 문구 | 번역 parity 검증 실패. 두 locale의 offline 실제 정책 화면과 revision 일치 확인 | +| P05 | clean offline 첫 실행→정책/문의 접근 | 번들 내용·확인된 연락처를 읽을 수 있음. 자동 외부 요청/브라우저 실행 0 | +| P06 | 정책 화면 진입·재개·locale 변경만 수행 | 외부 URL 호출 0, 고정 URL은 명시 tap 때만 OS 위임 | +| P07 | URL query/fragment에 schedule/profile/ID 삽입 시도 | 검사 실패. handoff 입력은 승인된 고정 URL, 개인정보/추적값 없음 | +| P08 | 브라우저 없음/외부앱 실패/취소/기기 offline | 안전한 KO/EN 피드백, 원래 앱 화면·로컬 데이터 보존, 네트워크 fallback/WebView 없음 | +| P09 | 200% text·screen reader·긴 EN 본문/링크 | 전체 본문/문의·링크 접근 가능, clipping/잘못된 semantics 없음. 실제 widget 및 두 OS 좁은 화면 확인 | +| P10 | document provider 취소/부분 실패·외부 동기화 지연 | 백업 성공/신고 설명이 실제 receipt와 일치. provider cloud 저장 완료를 주장하지 않음 | +| P11 | reset cancellation 실패·pending journal·재실행 recovery | 완전 삭제 단정 금지. 현 설치 pending 범위 표시, 외부 backup/구 server/mail은 별도 | +| P12 | exported backup 존재한 채 Local Data Reset | 외부 파일이 자동 삭제된다는 문구 없음. 실제 삭제 시험은 소유 synthetic fixture에만 수행 | +| P13 | policy URL HTTP 200이나 login HTML/옛 본문/PDF/redirect loop | 정책 접근/내용 검사 실패, status code만 pass로 처리하지 않음 | +| P14 | source HTML 새 revision, CDN/public locale 옛 revision | public pending 유지. 원격 visible text+revision/hash의 차이를 기록 | +| P15 | 콘솔 draft 저장만 성공, review/public 미반영 | 단계별 상태 기록. O02 공개 적용 완료 금지 | +| P16 | backend retention 1년/90일/30일·지원 주체·연락처 미확인 | 과거 값을 현행 사실로 승인하지 않음. 확인 필요 owner/근거/제출 차단 항목 기록 | +| P17 | 대상 국가/연령/운영주체 불명 | 법령 적용·아동 대상 여부/면제 추정 금지, 사실 확인 후 공식 기준 검토 | +| P18 | 사용자 명시 지원메일/hosting log가 존재 | 실제 처리와 app runtime collection을 분리한 항목별 근거 요구. 무조건 제외/무조건 동일화 모두 검토 실패 | +| P19 | 암호화 백업 header/파일명/provider metadata 노출 | 암호화≠전체 익명/E2E 예외 자동 적용. 실제 키·수신자·metadata 경계 검토 | +| P20 | screenshot/metadata가 계정·서버 sync·GPS·항상 정시 알람을 암시 | final RC 기능/플랫폼과 불일치 실패. synthetic 실제 RC 캡처로 교체·hash 기록 | +| P21 | 사전 제작한 fake console/URL fixture 검사 통과 | 테스트 레벨만 pass. 현재 public/console 검증으로 승격 불가 | +| P22 | public/console 적용 뒤 원격 값이 제안과 다름 | authoritative read-back 차이 기록, 완료 차단. 재시도 전 실제 상태/권한 재확인 | + +UI 코드 변경은 행동 중심 widget/native 좁은 검증을 추가한다. 문서만 고친 것을 검증하기 위해 구현을 그대로 복제하는 테스트나 대형 법률 판정 프레임워크를 만들지 않는다. 링크·필수 identity·revision mismatch 등의 문서 검사에는 의도적인 negative fixture를 사용하되 실제 관찰과 결과 수준을 분리한다. + +## 의존성과 소유 범위 + +| 이슈 | O02와의 관계 | +| --- | --- | +| O01 | 문서 current/historical 구분·진입점 정비. 실제 개인정보 문구/양식/공개 상태는 O02 소유 | +| R09 | 최종 binary의 SDK/네트워크/외부 위임 관측·한계. O02가 source 문장으로 대체할 수 없음 | +| R04/R08 | 검증한 release artifact identity와 store 관계. 실제 signing/store 결과를 정확히 연결 | +| T01/T02 | offline cold start·native file/권한/키/실제 모바일 기능 증거. mock/channel 테스트로 대체 금지 | +| D03/C01/A09 | reset·복원·backup의 partial/commit semantics. 현행 정책 문구가 receipt와 일치해야 함 | +| A11/A12/A13/A14/A15 및 U05/U08 | 노출 정보·권한/OS 정책·localization/accessibility의 현행 구현을 소비. 관련 이슈가 미완료이면 관찰된 범위만 주장 | +| O03/O05 | 실제 diagnostics 및 운영주체/지원 책임. 미구현 진단·미확정 담당자를 정책 사실로 쓰지 않음 | + +O02는 기존 backend 폐기·기존 계정 삭제·개인 메일 조회·hosting 교체·스토어 rollout·새 서버 기능·법적 인증을 수행하지 않는다. 필요한 원격 변경은 검토 가능한 정확한 payload와 대상이 준비된 뒤 권한 범위에서 진행한다. + +## 수용 기준 + +- [ ] 고정된 최종 source/lock/Android·iOS artifact 및 실제 store 배포 범위를 구별한 inventory가 있다. unknown과 N/A의 근거가 있다. +- [ ] F01–F11의 처리주체·목적·수신자·보존/삭제·보호·사용자 선택·스토어 정의별 판단이 실제 근거와 연결된다. +- [ ] legacy server 버전/계정·삭제 경로를 확인 없이 삭제·폐기·수집 없음으로 선언하지 않았다. 기존 원문과 당시 증거가 보존된다. +- [ ] 현재 정책 revision의 KO/EN 번들·확인된 문의·시행일·운영주체가 정비되고 offline 접근/접근성 검증을 통과한다. +- [ ] 승인·유효성이 확인된 policy URL만 tap 기반 OS 외부 navigation으로 제공하며 자동 요청·WebView·민감값 전달이 없다. 실패 후 앱 상태를 보존한다. +- [ ] final RC 데이터 흐름과 Play/Apple 실제 적용 범위의 field-answer old→new 제안이 완전하다. source-only ‘수집 없음’ 판정이 없다. +- [ ] 실제 지원/hosting/연령/관할/retention 사실과 관련 승인·책임 미확인이 해소되거나 명시 pending으로 남아 있으며, pending을 두고 전체 완료하지 않는다. +- [ ] KO/EN listing·실제 RC screenshots가 계정/서버/위치/알람 기능을 오도하지 않고 identity/hash·locale 증거가 있다. +- [ ] P01–P22 검증 결과가 테스트 레벨별 기록된다. 실제 실행이 필요한 행에 fixture만 있는 상태는 pass가 아니다. +- [ ] 권한 있는 공개 적용 뒤 public URL·본문 revision·무로그인 접근 및 console saved/submitted/reviewed/public 각각 authoritative read-back을 저장했다. +- [ ] 해당 배포 범위에서 실제 공개 정책·label·metadata가 검토된 최종 데이터 흐름과 일치한다. 외부 게시/신고/검증이 남으면 repo 구현 상태만 별도 기록하고 O02는 OPEN이다. +- [ ] 법률 적합성·영구 무통신·모든 OS 데이터이동 차단을 포괄 보증하지 않았으며, 구체적 증거와 남은 한계를 기록했다. + +## 실제 grill-with-docs 전체 대화 + +### 질문 1 — 신고 범위·구 서버형 버전 + +**agent 실제 질문** + +> O02 질문 1 — 신고 범위를 ‘새 local-only RC 하나’로 한정하면 안 되는 점부터 확정하고 싶습니다. 권고: R09가 식별한 최종 Android/iOS 바이너리 데이터 흐름과 별도로 Play package club.devkor.ontime의 현재 배포 대상 버전·track·지역 및 Apple app record/platform을 read-only inventory로 확인한 뒤 신고 범위를 정합니다. Google 공식 문서는 package 전체 현재 배포 버전의 관행을 합친 답변을 요구하고 internal-only track 예외를 명시하므로, 구 서버형 버전이 closed/open/production에 남았는지 unknown인 상태에서 ‘수집 없음/계정 없음’으로 덮지 않습니다. 구 계정 삭제 URL/retention 원문은 과거 증거로 보존하며 기존 서비스·사용자 데이터가 실제 정리됐다고 추정하지 않고, 현재 Local Data Reset과 구 서버 계정 삭제 의무를 구분합니다. 새 앱에는 로그인/서버 기능을 되살리지 않습니다. 콘솔 접근 불가 또는 구 서비스 처리 주체 미확인인 행은 pending, 소스 문서 정비만으로 O02 완료 금지입니다. 이 범위로 확정할까요? 근거: Data-Safety.md는 2026-05-10 saved 답변(이름/email/ID/diagnostics/FCM)을 현재로 표시, Privacy-Policy-Draft/계정삭제 문서는 1년/90일/30일 보존을 아직 owner 미승인으로 담고 있습니다. 공식 확인: https://support.google.com/googleplay/android-developer/answer/10787469?hl=en ; https://developer.apple.com/help/app-store-connect/manage-app-information/manage-app-privacy . + +**root 실제 답변** + +> Q1 채택합니다. 최종 local-only RC의 R09 데이터흐름과 스토어 신고의 실제적용범위를 구별하고, Play package의 현재배포버전/track/지역·Apple app record/platform을 read-only로 확인해야 합니다. 공식 문서의 package 전체 배포관행/내부track 예외를 현재조건으로 해석하며, 과거 서버형 버전이 남아 있는지 unknown이면 수집없음/계정없음으로 덮어쓰기 금지. 구 URL/retention원문은 역사증거 보존, 실제서비스폐기·기존사용자데이터삭제·운영주체 확인없이 완료추정금지. LocalDataReset은 현설치 데이터초기화이고 구서버계정삭제 처리와 다름. 새앱 로그인/server기능복구 금지. owner미승인 보존기간/처리주체/연락처는 root가 임의승인하지 않고 확인필요값으로 남깁니다. 접근불가/배포범위/구서비스책임 미확인은 pending, 문서수정≠O02전체완료. 실제 외부신고·법률문구 게시 전 exact payload/대상/적용범위와 확인된근거가 검토가능해야 합니다. + +### 질문 2 — 번들·공개 정책 및 외부 처리 경계 + +**agent 실제 질문** + +> O02 질문 2 — 현재 앱에 번들된 한국어 정책과 공개 정책·문의 처리의 경계를 어떻게 고정할까요? 권고: KO/EN 번들 정책은 비행기 모드에서도 읽을 수 있게 유지하고, 현재 고정 한국어 본문/2026-08-28 시행일/‘배포 페이지 연락처’만 있는 상태를 검증된 entity·정책 버전·실제 문의 경로로 정비합니다. Apple 5.1.1은 앱 안의 접근 가능한 privacy policy link를 요구하므로, 승인된 public HTTPS URL을 사용자가 누를 때만 OS 외부 브라우저로 여는 링크를 번들 본문에 추가하는 안입니다(새 WebView/자동 fetch/로그·일정·프로필 query 전달 없음). 일상 데이터·암호화키·OS 알림/복구 journal·사용자 명시 백업/외부 provider·support email·정책 hosting 접속기록을 각각 inventory하며, 외부 provider/browser/mail의 처리와 OnTime 자동 runtime 요청을 구별합니다. 특히 ‘수집 없음’은 local-only source 문구만으로 확정하지 않고 support/host 로그의 실제 담당자·보존·제3자 사실과 양 store의 정의에 대조합니다. 암호화된 백업이라고 모든 metadata까지 익명/E2E 면제라 단정하지 않습니다. 과거 1년/90일/30일을 복사하거나 ‘초기화하면 내보낸 백업·이전서버·메일도 삭제’라고 쓰지 않습니다. 연락처/연령/관할/retention 미확인 값을 임의로 정하지 않고 owner 확인 대기와 제출차단을 명시합니다. 이 정책·제품 UI 보완 범위를 O02에 포함할까요? 공식 근거: https://developer.apple.com/app-store/review/guidelines/#privacy ; https://support.google.com/googleplay/android-developer/answer/10144311?hl=en ; https://developer.apple.com/app-store/app-privacy-details/ . + +**root 실제 답변** + +> Q2 포함 채택합니다. KO/EN 번들 정책은 offline 접근 유지, 확인된 운영주체/문서버전/시행일·문의경로로 정비하며 임의미래날짜·연락처 생성 금지. 공개 HTTPS URL이 실제 승인·접근가능·현재정책과동일함을 확인한 뒤 사용자 tap만 OS외부browser 위임하는 policy link 추가를 O02의 좁은 UI범위로 허용합니다. URL 미확인시 임의주소·깨진링크로완료금지. 자동fetch/WebView/network권한/원문query/추적SDK 없음, 취소·offline·외부앱실패후현재앱보존/KOEN접근성 검증. 데이터·key·journal·명시백업/OSprovider·support메일·policyhosting을 각 처리주체/목적/보존/외부수신자 기준으로 inventory. store 'collection/sharing/tracking' 정의 및 조건부예외에 맞춰 항목별 판단하고 외부사이트로그/자발support를 앱자동수집과 일괄동일시하지도, 무조건신고면제하지도 마세요. 암호화≠자동익명/E2E면제, reset≠exportedbackup/구서버/mail삭제. owner/연령/관할/retention 미확인은 제출차단·확인필요, root가 사용자 대신 법적사실을 만들어 승인하지 않습니다. 실제공개게시·콘솔제출은 검증된payload/대상준비후권한판단 경계 유지. + +### 질문 3 — 완료·원격 read-back·실패 행렬 + +**agent 실제 질문** + +> O02 질문 3 — 구현 완료와 공개/스토어 적용 완료를 구별하는 검증·종결 기준을 확정하겠습니다. 권고: (1) final source/lock/Android AAB·설치split·iOS archive/IPA 식별자·hash·version/signing·R09 관측·T01/T02 실제 기능 증거를 데이터흐름 row에 연결, (2) 동일 policy revision의 KO/EN 번들·공개HTML·Play/Apple 정확 field-answer/metadata/screenshot 변경안과 old→new diff를 준비, (3) 권한이 확보된 적용 뒤에는 URL redirect/HTTP/content/문서hash/무로그인 접근, console saved→submitted→reviewed→public 상태를 각각 read-back하는 기준입니다. 공개URL/콘솔을 조사하지 못한 현재는 unknown이며 과거 2026-05-10 체크박스 재사용 금지. 가짜 publicURL/콘솔 fixture는 검사기 음성시험만 증명합니다. 실패행렬에는 구버전 서버수집 잔존, final binary SDK변경, 영어만 구정책, 브라우저 자동열림/query누출, 백업provider cancel, reset pending인데삭제완료단정, 링크200이지만login/옛본문, 소스수정후CDN옛본문, draft저장만하고public완료표기, 무권한콘솔, 미확인연령/주체/retention을 포함합니다. 외부게시/신고가 대기면 repo구현 완료를 따로 적고 O02전체는 OPEN 유지합니다. 대상국가·연령과 실제 문의운영 사실이 확인되면 해당 공식 법령/가이드의 적용 검토를 추가하되 이번 기술감사를 포괄 법률적합성 보증으로 표현하지 않습니다. 이 완료 경계를 채택할까요? + +**root 실제 답변** + +> Q3 채택합니다. 최종소스/lock 및 실제 배포범위별binary identity·R09/T01/T02 증거를 데이터흐름 row에 연결하고, 정책revision 일치하는 KOENbundle/publicHTML/각storefield-answer·metadata 변경안 old→new를 준비. 실제권한있는적용후 URLredirect/status/contenthash/무로그인접근과 saved/submitted/reviewed/public 각각 readback, 200/저장성공만으로 공개완료 금지. 현재공개URL/콘솔미조회는unknown, 옛2026-05-10증거 소급재사용 금지, fixture는검사기수준만. 제시negative행렬 모두포함, signedSDK/source변경이면 해당데이터흐름판정 재검토. 외부신고대기 시 repository구현과 전체O02OPEN 구별. 운영주체/연령/대상국가/retention 실제정보미확정은 명확한 확인필요목록으로 남기고 확인된적용조건의 공식법령/가이드만 검토, 포괄 법률보증 금지. 모든 실제질문/답/보충·정정, 현재확인된근거/unknown, 정확AC/의존성으로 동결해주세요. + +현재 설계 세션에 추가 보충 질문·철회·정정은 없었다. 이후 구현 검토에서 새 결정이나 정정이 발생하면 이 실제 대화 뒤에 추가하고 원문을 소급 편집하지 않는다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 77853ef9..d1767f82 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -965,16 +965,18 @@ "Data Safety 문서가 기존 계정·FCM·원격 analytics와 계정 삭제 URL을 설명한다. 현재 binary와 store 신고가 일치하는지는 확인해야 한다.", "`docs/Google-Play-Data-Safety.md:64-86,122-152`. final binary의 데이터 흐름 표를 기준으로 privacy policy·store metadata·screenshots 검토 후 store 화면 read-back. 이번 감사는 법률 적합성이나 스토어 위반을 판정하지 않음." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_o02", + "issue_number": 622, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/622", "decisions_file": "plans/audit-2026-09-23/issues/O02.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 35 + "sequence": 35, + "readback_verified": true, + "issue_body_sha256": "fb8fbd7c55f2cba96f7d80b87d071da57a9458dfe55f651f0266339223500e4d" }, { "audit_id": "D06", From da7e6e34f33cd1fc5b349b51647a2c9f9df173cc Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 12:33:59 +0900 Subject: [PATCH 53/59] docs: prepare restore runtime integration after atomic saves --- .../a09-implementation-preflight.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 plans/audit-2026-09-23/a09-implementation-preflight.md diff --git a/plans/audit-2026-09-23/a09-implementation-preflight.md b/plans/audit-2026-09-23/a09-implementation-preflight.md new file mode 100644 index 00000000..044818bd --- /dev/null +++ b/plans/audit-2026-09-23/a09-implementation-preflight.md @@ -0,0 +1,23 @@ +# A09 implementation preflight + +Status: preparation only, 2026-09-24. A08 is still implementing and exclusively owns product files and Flutter commands. Do not start A09 product changes until A08's source freeze, final local verification, source commit and handoff. A09 #602 remains the full accepted scope; this note records current integration points, not additional approval or passed tests. + +## Current evidence + +- C01 source `4529d9f84e911b1ee2fbb42af09698a0c27cc9f2`, remote proof HEAD `aa2fda68fbf3ac431738de3792f5d587d35b6dda`: workflow receipts and exclusive preview validation exist. Final remote Flutter 959 tests, 88.72%, Android APK/manifest success. Actual encrypted staging and full runtime replacement remain pending. +- `BackupService._claimRestore` claims the existing `LocalDataOperationGate` before preview generation/revision validation, then increments the memory generation. `_applyRestore` cancels old alarms before its database transaction; the transaction rechecks revision, replaces rows, and imports source revision plus one. This revision is not an installation-wide monotonic epoch. +- The current candidate holds parsed data in memory. Do not call this actual encrypted staging proof. +- `PreparationWithTimeLocalDataSourceImpl` uses the `preparation_with_time_` namespace and validates SharedPreferences write/removal booleans. `EarlyStartSessionLocalDataSourceImpl` uses `early_start_session_` and still ignores removal booleans. Inventory all runtime namespaces rather than only restored schedule IDs. +- `NotificationTapRouter` currently tags receipt with the memory generation at reception. Late old OS delivery requires an installation-persistent identity, not retagging it with current generation. +- `LocalDataLifecycle.bootstrap` handles D03 reset recovery and the historical local-only cutover. Reuse the reset journal/OS owner and typed startup recovery boundaries. Do not reuse cutover's blanket preference cleanup as restore cleanup. +- A08 WIP schema 3 introduces installation-local `Users.storeIncarnation` and aggregate identities/receipt metadata, excluded from portable backup versions 1/2. Its restore path inserts a fresh profile so identity is reissued. Treat this as WIP until final source handoff. Prefer this authoritative store identity where suitable instead of introducing an independent second epoch with conflicting semantics. + +## Next implementation sequence + +1. Read A08's final actual transcript, ADR 0037, source manifest and final validation. Recheck current gate, receipt, writer and watch behavior from that committed source. +2. Build an explicit writer/runtime/OS ownership inventory. Separate precommit quiescence, one durable commit, postcommit cleanup and restart recovery; no timer timeout counts as native cancellation. +3. Resolve the minimal persisted cleanup marker and staging-key/path lifetime against the existing encrypted database opener and platform backup exclusion. Any new migration must follow final A08 schema, preserving v1/v2 portable backups and historical database fixtures. +4. Add behavior-first negative evidence for same-ID/fingerprint restored data reviving old runtime and late OS tap acceptance, then implement actual staging and replacement through existing C01/A15/D03 ownership. +5. Verify rollback before commit, partial completion after commit, actual file/DB reopen or host process boundaries, all old runtime namespaces, source identity, KO/EN receipt UI and complete regression. Host fixtures, native compilation and real mobile execution are distinct evidence. + +No staging files, runtime cleanup, database replacement, GitHub policy changes or device actions were performed for this note. D02 damaged-database replacement, D05 input bounds and D07 template timestamps remain separately tracked dependencies; do not conceal unresolved data round-trip differences by weakening checks. From a4dfbe426b33815c96386fe3cb6af22186daf98a Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 12:37:13 +0900 Subject: [PATCH 54/59] docs: define backup reminder age and snooze behavior --- handoff/audit-2026-09-23-progress.md | 2 + plans/audit-2026-09-23/README.md | 3 +- plans/audit-2026-09-23/issues/D06.md | 189 +++++++++++++++++++++++++++ plans/audit-2026-09-23/state.json | 12 +- 4 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/D06.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index b2180993..7739e575 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -138,3 +138,5 @@ A08 진행 메모: root는 schema3/store+aggregate identity 외에 안정된 rec O01#621 실제3문답·파일inventory/모바일실행·15개검증 기준 게시/readback완료,35/64(미생성29). O02 전담 /root/grill_o02 시작. A08 초기 analyzer compile error0, 경고14 정리 중; historical schema2는 baseline4529 실제DB DDL 추출, schema1 ccc6272와 차이 확인. 집중/전체 최종검증 아직 미완료. source freeze 전 WIP는 구현agent 소유, root Flutter금지. 마지막 검증된 원격 제품은 C01 aa2fda68이며 현재 로컬 docs HEAD9682c34e 이후 관리문서 추가 중. O02#622 실제3문답·데이터흐름11·검증22개 기준 게시/readback완료,36/64(미생성28). 공개URL/스토어 현재상태는 미조회 unknown이며 외부정책 변경 없음. 새 D06 전담 /root/grill_d06 문답 착수. A08 중간 focused52/migration5 통과, 새초안 stale saveReceipt 경계와 watch rollback 검증 보완 중. Q4 내부 recurring create의 조용한 중복성공을 typed conflict로 교체하고 미사용 save 우회 경로 제거 채택; 제품 중복성공은 aggregate durable receipt 경계로 한정. 최종 sourcefreeze/전체검증 전 제품 WIP 커밋 금지. + +D06#623 전담실제4문답·27개검증·10개AC 게시/readback완료,37/64(미생성27). 첫미백업commit/cutoff중편집/metadata-onlyretry/legacyunknown/restore시점/7일숨김·시계한계 확정. ADR0035보충안은이슈에준비,공유ADR아직미적용. D07 새전담 /root/grill_d07 착수. A08 추가root검토3건(create default+baseline혼합snapshot, 모달oldowner응답, place/template/default의recurring root receipt무효화)을담당이실제경계로확인해수정중. focused52/migration5외최종회귀·UI·full아직미완료. A09 구현preflight를da7e6e34에보존, A08freeze후이어갈예정. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 470ea2b1..df543fd5 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -52,8 +52,9 @@ | R09 | [#620](https://github.com/DevKor-github/OnTime-front/issues/620) | 실제 3문답·최종 APK/IPA 구성/오프라인/트래픽 귀속·20개 검증 행렬 확정 | | O01 | [#621](https://github.com/DevKor-github/OnTime-front/issues/621) | 실제 3문답·현행/역사 문서 inventory·두 모바일 재현·15개 검증 행렬 확정 | | O02 | [#622](https://github.com/DevKor-github/OnTime-front/issues/622) | 최종 바이너리와 실제 배포 범위에 맞춘 개인정보 정책·스토어 신고 및 공개 검증 | +| D06 | [#623](https://github.com/DevKor-github/OnTime-front/issues/623) | 실제 4문답·최초 미백업 age/동시 export/7일 숨김·27개 검증 기준 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **36/64개**를 게시하고 원격 본문 동일성을 확인했으며 **28개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했다. C01은4529d9f8로 구현해 최종 원격959 tests·88.72%·APK를 통과했고 A08 구현 중이다. A08의 현재 부분 검증은 집중52개와 역사 migration5개이며 최종 전체 검증은 남아 있다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **37/64개**를 게시하고 원격 본문 동일성을 확인했으며 **27개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했다. C01은4529d9f8로 구현해 최종 원격959 tests·88.72%·APK를 통과했고 A08 구현 중이다. A08의 현재 부분 검증은 집중52개와 역사 migration5개이며 최종 전체 검증은 남아 있다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/D06.md b/plans/audit-2026-09-23/issues/D06.md new file mode 100644 index 00000000..87dffdf6 --- /dev/null +++ b/plans/audit-2026-09-23/issues/D06.md @@ -0,0 +1,189 @@ +# [P2][D06] 최초 미백업 변경부터 30일을 계산하고 Home·My Data에 닫을 수 있는 백업 안내 제공 + +## 문제와 결과 + +현재 성공한 백업 이후의 변경이 남아 있을 때 백업 안내는 `lastDurableDataAt`에서 30일을 계산한다. 사용자가 매일 일정을 수정하면 이 시각이 매일 바뀌므로, 한 달 이상 백업하지 않은 변경이 있어도 안내가 계속 뒤로 밀린다. 안내는 My Data 안의 텍스트에만 있고 닫기나 재표시 정책이 없다. ADR 0035가 약속한 dismissible, non-blocking in-app reminder를 충족하지 못한다. + +이 이슈는 **성공한 export의 Backup Cutoff 이후 최초 durable commit부터 30일을 세고, 정상 Home과 My Data에서 비차단 안내 및 7일 숨김을 제공**한다. export 중 새 편집, 파일 저장 뒤 상태 기록 실패, 복원·초기화, 기존 설치의 알 수 없는 과거 시각, 앱 재시작과 기기 시계 변경까지 같은 경계에서 처리한다. 백업 파일이 외부에 계속 존재하거나 안전하게 동기화됐다고 보장하지 않는다. + +우선순위 P2. 데이터 유실을 직접 일으키는 A02/A08/A09/D01/D02 및 이들의 transaction/lifecycle 계약이 선행한다. 이 문서는 전담 `/root/grill_d06`이 grill-with-docs를 사용해 root와 한 번에 한 질문씩 실제 네 문답을 완료한 **구현 계획**이다. 제품 구현·테스트 통과·기기 확인 보고가 아니다. + +## 조사 기준과 증거 + +고정 조사 SHA는 `f5a41ccfe57f58f4cff40df3f635fbe9cf84a284`이다. 아래 BackupService/UserDao/MyData/backup entities/workflow는 조사 당시 working tree에서 이 SHA와 차이가 없었다. 병행 A08 WIP에는 schema 3 및 `Users.storeIncarnation`이 보이지만 아직 이 SHA의 committed 제품 상태가 아니다. D06은 그 파일을 수정하거나 Flutter/codegen/test를 실행하지 않았다. 실제 구현은 A08/D04 및 A09/D02가 완료된 최신 schema/저장소 identity에 맞춰 진행한다. + +| 코드·문서 | 실제 확인한 사실 및 한계 | +|---|---| +| [BackupService freshness](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/core/backup/backup_service.dart#L275) | export 기록이 없으면 firstDurableDataAt, 기록이 있으나 revision이 다르면 lastDurableDataAt을 사용한다. `DateTime.now().difference(value).inDays >= 30`이며 주입 clock이 없다. | +| [UserDao durable commit](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/data/daos/user_dao.dart#L61) | revision 증가, 최초 시각 COALESCE, 마지막 시각 덮어쓰기다. 최초 미백업 변경/dirty episode/숨김 기록은 없다. 여러 repository가 이 경로를 호출한다. | +| [UserDao markExported](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/data/daos/user_dao.dart#L181) | snapshot revision/cutoff를 기록한다. 뒤의 첫 미백업 시각을 보존하는 모델이나 오래된 metadata-only retry를 구별하는 계약이 없다. | +| [Users table](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/data/tables/user_table.dart) | dataRevision, lastExportedRevision/At, first/lastDurableDataAt만 존재한다. 과거 최초 미백업 시각을 정확하게 역산할 수 없다. | +| [파일 export](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/core/backup/backup_service.dart#L74) | snapshot→암호화→native export receipt→generation 확인→markExported 순서이며 gate는 일반 편집을 막지 않는다. `savedFreshnessUpdateFailed`로 외부 저장/로컬 기록 실패를 구분한다. | +| [restore profile](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/core/backup/backup_service.dart#L242) | revision을 source revision+1로 만들며 first/lastDurableDataAt에 source cutoff를 넣는다. 현 설치의 복원 commit 시각과 다르다. revision 숫자는 서로 다른 저장소의 동일성을 증명하지 않는다. | +| [MyDataScreen](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/presentation/my_page/my_data_screen.dart#L66) | C01의 workflow/current request/generation으로 freshness 오류 및 늦은 결과를 방어한다. reminderDue이면 Text만 표시하며 닫기와 Home 공유 상태는 없다. C01 구현을 유지해서 확장한다. | +| [Home](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/presentation/home/screens/home_screen.dart) | 일정·점수 UI가 있으나 backup 안내는 없다. active Preparation Run 진입은 기존 ScheduleBloc 경로다. 카드가 이 흐름을 가로막지 않아야 한다. | +| [Backup result/workflow](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/domain/entities/backup_operation.dart), [workflow](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/lib/domain/use-cases/local_data_workflows.dart) | export 결과는 enum이며 restore에는 receipt/retry 경계가 있다. export metadata-only retry는 현재 구현됐다고 주장할 수 없다. 같은 C01/A02 경계를 확장해야 한다. | +| [기존 backup test](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/test/core/backup/backup_service_test.dart#L76) | export 중 일반 편집이 남으면 captured revision만 기록하는 실제 DB 회귀는 있다. 그 테스트는 최초 미백업 age나 30일/7일 안내를 검증하지 않는다. | +| [ADR 0035](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/docs/adr/0035-track-backup-freshness-without-automatic-backup.md), [CONTEXT](https://github.com/DevKor-github/OnTime-front/blob/f5a41ccfe57f58f4cff40df3f635fbe9cf84a284/CONTEXT.md) | 30일 미백업 변경 안내, 현재 설치에서 실제 성공한 export만 freshness 해소, 자동 백업/OS 알림 없음, 외부 파일 존재 보장 없음이 기존 계약이다. | + +## 확정한 age·snapshot 계약 + +1. `Backup Freshness`의 Never Exported / No Changes Since Export / Unexported Changes 의미를 유지한다. 마지막 변경일과 최초 미백업 변경 시각을 혼용하지 않는다. 안내용 dirty episode는 성공한 export cutoff 이후 최초 미백업 durable commit에서 시작해 그 변경들이 성공 export에 포함될 때 끝난다. 구현 상태 식별이며 새 제품 glossary 항목은 만들지 않는다. +2. 30일은 UTC instant 기준 720시간이다. 테스트 가능하도록 clock을 주입한다. 매일 추가 변경해도 episode의 최초 시각은 밀리지 않는다. no-op, rollback, timer snapshot, 알림 registry, 조회, 화면 진입, 안내 닫기는 durable content revision/age를 바꾸지 않는다. +3. 현재 설치의 durable revision, 최초 미백업 revision/시각, 단일 export snapshot 경계 및 그 이후 첫 durable commit을 원자적으로 구별하는 **최소 유계 installation-local metadata**를 사용한다. 매 revision 전체 변경 이력을 영구 누적하지 않는다. 구체 저장 구조는 최신 schema와 gate 소유권을 확인한 뒤 구현한다. +4. 예: 1일에 A를 수정하고 35일에 revision 10 snapshot을 export하는 중 B를 수정했다면, 저장 성공 뒤 A의 오래된 age는 해소하고 B가 commit된 35일의 시각을 새 age로 남긴다. B 뒤 C를 계속 수정해도 B의 시각을 유지한다. export 중 일반 편집을 전역 busy로 막아 이 문제를 회피하지 않는다. +5. provider 성공 확인과 freshness metadata commit은 별개다. 취소/실패/metadata commit 실패는 기존 age를 해소하지 않는다. provider가 파일을 저장한 사실은 A02/C01 partial receipt로 유지하며 동일 attempt의 허용된 metadata-only retry는 provider를 다시 호출하거나 암호를 다시 요구해 재수출하지 않는다. +6. retry receipt는 원문 없는 attempt identity, store incarnation, snapshot revision/cutoff 및 당시 이후 첫 변경 경계에 묶인다. retry 전 새 변경이 생겨도 그 age를 보존한다. 이후 새 export가 성공했거나 restore/reset으로 store가 달라졌다면 옛 receipt가 최신 cutoff, first dirty, snooze를 되돌리지 못한다. superseded/obsolete 결과를 명시하고 동일 시도 재호출도 멱등적으로 처리한다. +7. 목적지 URI/경로/권한, 비밀번호, 원문은 metadata나 receipt에 보관하지 않는다. native 저장 성공 증거를 잃은 프로세스 재시작에서는 파일 저장을 성공으로 추정하지 않는다. 재시작 전 이미 영속화한 freshness 성공은 정상 DB read-back으로 유지한다. +8. 각 durable writer의 실제 transaction에서 revision/age가 함께 commit/rollback해야 한다. schedule aggregate/반복 일정/준비·템플릿/profile·설정/결과 경로를 목록화한다. 기존 구조의 revision writer를 재사용하며 별도 일반 transaction framework를 만들지 않는다. metadata만 쓰는 동작 때문에 content watch가 거짓 사용자 변경을 만들지 않게 한다. + +## migration·복원·초기화 + +| 상태 | 정책 | +|---|---| +| 새 설치, 자동 빈 profile만 존재 | reminder 없음. 일정 row 수만으로 데이터 유무를 추정하지 않는다. | +| 사용자의 최초 durable commit | 현재 설치 commit 시각부터 30일. onboarding/설정/기본 준비/템플릿/결과 등 전체 backup 범위를 포함한다. 자동 기본값과 사용자 명시 저장의 동일 값은 기존 durable/no-op 계약을 따른다. | +| legacy 현재 revision == 기록된 성공 export revision | 기존 clean 상태 유지. migration 자체가 dirty를 만들지 않는다. | +| legacy 데이터가 있고 정확한 첫 미백업 시각 근거 없음 | age unknown으로 보존. 첫 정상 진입부터 비차단 안내 허용. ‘백업하지 않은 변경이 있습니다’와 같이 표시하고 정확한 일수 또는 ‘30일 이상’을 주장하지 않는다. lastDurableDataAt을 정확한 최초 시각으로 재명명하지 않는다. | +| 성공한 정상 복원 A09 / 손상 저장소 복원 D02 | Never Exported, 현재 설치의 복원 commit 기준으로 새 30일 시작. 외부 source cutoff/export 이력/숨김을 가져오지 않는다. 백업은 현재 설치의 성공 export 증거가 아니다. | +| 복원 취소·commit 전 실패 | 원본 freshness/age/snooze 보존. staging 검증만으로 메타데이터 변경 없음. | +| 복원 commit 후 후속 정리 실패 | 새 데이터/새 age는 보존. C01/D01 제한 상태에서는 reminder를 띄우지 않는다. 후속 정리 재시도가 새 복원/age 재설정이 아님. | +| Local Data Reset | 설치 소유 age/snooze/pending metadata도 제거. 외부 백업 삭제를 약속하지 않는다. old async receipt는 새 빈 profile을 변경하지 못함. | + +신규 metadata는 portable backup format 1/2에 포함하지 않고 암호 suite도 바꾸지 않는다. D04의 versioned migration/snapshot/파일 fixture 경로로 설치 데이터를 보존하고 실패 rollback을 검증한다. A08의 병행 schema 3을 선점해 수정하지 않으며, 실제 구현 착수 시 최신 schema에서 새 migration을 결정한다. A08 store incarnation과 A09/D02의 active store/runtime 세대를 재사용해 같은 revision 숫자의 다른 저장소를 구별한다. + +## 안내와 7일 숨김 + +- 정상 Home 목록에 inline 카드, My Data에는 freshness를 항상 표시하고 동일 reminder 카드를 공유한다. 모달·진입 차단·전역 overlay를 만들지 않는다. ‘백업하기’는 기존 My Data 화면으로 이동만 하며 password prompt/picker를 자동 실행하지 않는다. +- ‘7일 뒤 다시 알림’은 168시간 snooze다. store/dirty episode identity에 결합하고 추가 변경·앱 재시작으로 해제하지 않는다. 단순 닫기 성공을 content revision 변경이나 백업 완료로 해석하지 않는다. +- export가 모든 현재 변경을 포함하면 episode/snooze 종료. cutoff 이후 변경만 남으면 새 episode/새 30일이다. 취소/실패/metadata 실패/백업 화면을 여는 행위는 age/snooze를 자동 갱신하지 않는다. metadata-only retry도 새 episode에 오래된 숨김을 붙이지 못한다. +- Home/My Data 간 공유 상태, 정상 resume, foreground에서 30일·7일 경계 도달 시 재평가한다. 단일 bounded timer를 기존 lifecycle/clock 구조에 연결하고 dispose/background 때 정리한다. 매초 DB polling, OS reminder, background job, 서버 시각 조회, 자동 export는 없다. +- startup, onboarding, Recovery Mode, active Preparation Run 위에는 표시하지 않는다. 안내를 숨겼다는 이유로 My Data의 수동 백업/복원 기능을 제거하지 않는다. 실패 상태가 데이터 손실 또는 백업 성공처럼 보이지 않게 한다. +- snooze 저장 실패 시 같은 mounted 화면에서 임시 숨김은 허용하되 ‘7일 숨김 저장 완료’를 표시하지 않는다. 안전한 오류와 명시적 재시도를 제공하고 재실행 시 다시 나타날 수 있음을 알린다. Home의 실패로 정상 일정 사용을 막지 않는다. +- freshness 읽기 실패는 clean이나 Never Exported로 임의 치환하지 않는다. My Data의 C01 재시도·늦은 request/generation 방어를 유지하며 Home에도 raw exception 없는 안전한 상태를 제공한다. reset/restore/route 변경 뒤 old Future가 새 화면 카드/오류/숨김을 덮지 못한다. +- KO/EN, 좁은 화면, 200% 텍스트, screen reader 이름·읽기 순서·버튼 초점, route pop/revisit, 중복 탭을 검증한다. 현재 시점·최초 미백업 age·마지막 export cutoff의 표시 의미를 분리한다. 외부 파일의 현재 존재/동기화 여부는 알 수 없다. + +### 기기 시계 변경 + +age가 음수면 0으로 clamp한다. 기기 시계가 앞으로 이동해 due가 되면 안내 기준으로만 사용하며 실제 720시간 경과를 입증한다고 주장하지 않는다. 신뢰 서버 시각은 도입하지 않는다. + +snooze start/end는 같은 UTC 정밀도로 기록·비교한다. 파싱 불가, end < start, 기간 >168h, now < start이면 잘못된 숨김을 유지하지 않고 due를 재평가한다. foreground에서 이전 관측보다 UTC가 역행한 경우 기존 snooze를 조기 종료한다. 정상 foreground에서는 최초 관측의 남은 기간(최대168h)을 monotonic 타이머에도 묶는다. 단순 submillisecond 표현/정규화 차이를 역행으로 오판하지 않는다. resume/재시작은 저장 경계와 현재 UTC를 다시 읽고 단일 timer를 재설정한다. + +프로세스 종료 중 start/end 내부로만 이동한 기기 시계 조작은 로컬 기록만으로 정확히 검출할 수 없다. 따라서 ‘실제 7일을 반드시 보장한다’고 하지 않는다. 이 한계를 숨기려고 계속 원격 시각을 가져오거나 백그라운드 추적을 추가하지 않는다. + +## ADR 0035 보충안 + +최초 미백업 시각을 알 수 없는 기존 설치에서 조용히 30일을 새로 기다리는 대안과 즉시 비차단 안내하는 대안, 복원 source cutoff와 현 설치 commit 중 기준 선택, durable snooze/clock 보수성은 실제 지속 상태·사용자 경험 trade-off다. 새 ADR 번호를 늘리기보다 기존 ADR 0035에 아래 문단을 추가하고 구현과 함께 검증한다. 이 전담 agent는 D06.md만 수정하도록 위임받았으므로 공유 ADR/CONTEXT를 지금 수정하지 않았다. 기존 Backup Freshness/Cutoff 용어로 충분하며 구현 필드·timer 정책을 glossary에 넣지 않는다. + +> Reminder age begins at the first committed durable change not represented by the latest successful export on this installation. A successful export clears only changes at or before its Backup Cutoff; changes committed while export is in progress retain their own earliest unexported time. Export cancellation, failure, or failure to record freshness does not clear that age. Export metadata and reminder dismissal are installation-local and are excluded from OnTime Backup. A restore starts a new Never Exported reminder period at its commit on the destination installation rather than inheriting the source cutoff or dismissal. For existing installations whose first unexported time cannot be established, OnTime may show a non-blocking reminder immediately without claiming that 30 days have elapsed. We choose this conservative notice over inventing history or postponing unknown unexported changes for another month. Normal Home and My Data share a seven-day dismissal tied to the current unexported-change episode; the freshness status and manual backup remain available. Thirty days and seven days mean 720 and 168 hours measured against the local UTC clock. Invalid dismissal intervals and observed clock reversal end suppression conservatively. Without a trusted external clock, OnTime does not promise exact real-world elapsed-time detection across process termination and clock changes. No system reminder, automatic export, external destination retention, or remote clock service is introduced. + +## 의존성·비범위 + +| 항목 | D06의 연결 | +|---|---| +| A02 #584 / A03 #586 | native 파일 저장 receipt·취소·실패·교차 복원 유지. provider 저장 성공과 metadata 실패를 나누며 metadata-only retry를 같은 경계에 추가한다. picker 수정 자체는 재작성하지 않는다. | +| C01 #600 | domain workflow/typed result/currentness를 재사용. UI가 DB·native export·retry 순서를 직접 조합하지 않는다. | +| A08 #601 / D04 #605 | atomic durable commit/revision과 store incarnation, 최신 DB migration에 연결. A08 자체 전체 완료를 D06으로 주장하지 않는다. | +| A09 #602 / D02 #604 | 정상/손상 복원 commit 시각, 새 저장소 identity, old async receipt 배제 및 runtime 제한 경계. source metadata 불승계. | +| D01 #603 / D03 #595 | startup/recovery 중 안내 없음, reset 설치 소유 metadata 정리 및 late callback 차단. | +| U08 #597 / U07 / T06 | 구체 카드의 KO/EN·text scale·접근성·유계 timer 검증. 앱 전체 접근성/레이아웃 완료와 구별. | +| F04 백업 안내 개선 후보 | 최근 성공 시점·비밀번호 분실 안내 등 더 넓은 후속 기능과 연결하되 이 D06은 age/reminder/snooze의 합의 범위로 완료 판정. | + +제외: 자동/클라우드 백업, 로그인/서버/분석 전송, 새 암호화·portable format, OS notification/권한 추가, 파일 존재 재검사나 목적지 재접근, 비밀번호 보관/복구, 사용자에게 모든 변경 revision 이력 표시, 별도 범용 journal/coordinator, production 데이터/파일을 사용하는 파괴 테스트. 기기 시계의 신뢰성이나 외부 provider 영구 보관을 보장하지 않는다. + +## 실패 시나리오와 검증 matrix + +실제 Drift transaction과 제어 가능한 export port/clock/lifecycle을 사용한다. 구현 메서드를 그대로 복제한 getter 테스트로 대체하지 않는다. 고정된 과거 구현에서 매일 변경 시 reminder가 안 나오는 negative를 먼저 재현하고 새 구현에서 해결됨을 보인다. native 확인은 소유한 합성 QA 데이터만 사용한다. + +| ID | 조건/실행 | 기대 결과와 증거 | +|---|---|---| +| D06-01 | clean export 후 첫 변경, 이후 31일 매일 변경 | 최초 변경+720h에 due. 마지막 변경일로 계속 지연되지 않음. | +| D06-02 | 719h59m59s, 720h, 720h+경계 | 경계 이전 숨김/정확 경계 이후 due. UTC/DST·시간대 변경으로 다른 기간이 되지 않음. | +| D06-03 | 자동 빈 profile / 일정 0이나 사용자 설정·기본 준비·템플릿 있음 | 전자는 안내 없음. 후자는 durable 의미에 맞는 age가 존재. rows count 단독 판정 금지. | +| D06-04 | no-op/실패 transaction/rollback/runtime snapshot/조회 | revision·최초 age·episode 모두 불변. 실제 실패 transaction 내 age 쓰기도 rollback. | +| D06-05 | export snapshot 뒤 B/C 편집, delayed provider success | cutoff 이하만 해소, B의 최초 commit 시각 보존, 새 episode/snooze 분리. 저장 파일은 cutoff snapshot만 포함. | +| D06-06 | export snapshot 뒤 편집 없음, 성공 | clean, episode/snooze 종료, 마지막 export 시각은 cutoff. | +| D06-07 | picker 취소/provider 오류/암호화 실패 | 기존 age/snooze 보존, 성공 안내 없음, gate 재사용 가능. | +| D06-08 | provider 성공 뒤 metadata write 실패 | 실제 파일 저장 partial receipt 보존, age 미해소, provider 호출은 1회. | +| D06-09 | metadata-only retry 전 새 편집 및 같은 retry 동시 탭 | 새 최초 변경 시각 보존, 단일 metadata commit 또는 멱등 완료, provider 추가 호출 0. | +| D06-10 | old metadata retry 전 새로운 export 성공 | old receipt가 최신 cutoff/first dirty/snooze를 되돌리지 않음. superseded 처리. | +| D06-11 | metadata retry 전 restore/reset, 숫자 revision/ID 동일 | store identity로 old receipt 거절, 새 metadata 불변. | +| D06-12 | export 성공 callback 유실/프로세스 종료 후 reopen | 성공 증거 없으면 추정 금지. 이미 DB commit된 freshness는 실제 파일 DB reopen으로 유지. | +| D06-13 | legacy clean / legacy dirty unknown / legacy 빈 설치 | clean 유지 / 즉시 안내 가능하나 30일·정확 일수 단정 없음 / 안내 없음. migration 실패 원본 보존. | +| D06-14 | 오래된 source cutoff의 format 1/2 restore | Never Exported, 현 설치 commit부터 30일. source dismiss/export metadata 불승계, durable 내용은 보존. | +| D06-15 | restore preview 취소/commit 전 실패/commit 후 cleanup failure | 전자는 원본 age 유지; 후자는 새 age 유지·제한 화면에 안내 없음. retry가 age를 재시작하지 않음. | +| D06-16 | snooze 뒤 추가 편집·Home↔My Data·프로세스 재시작 | 같은 episode에 168h 숨김 공유, 기본 freshness/수동 backup 접근 유지. | +| D06-17 | snooze 중 전체 export 성공 / cutoff 뒤 변경 남음 | 전체면 종료, 남으면 새 episode의 새 30일. old snooze를 새 episode에 복사하지 않음. | +| D06-18 | snooze 저장 실패/읽기 오류/재시도 | persisted 성공 위조 없음, mounted 임시 닫기·안전 오류·재시도, 재실행 재표시 가능. content revision 불변. | +| D06-19 | Home foreground 유지하며 30일/7일 경계, background/resume | timer 하나, 정확 경계 재평가, background/dispose 정리, resume 최신 DB/read-back. 매초 polling 없음. | +| D06-20 | snooze start 이전 시계역행/구간역전/>168h/파싱 오류 | 잘못된 숨김 종료 및 due 재평가. 원문 exception이나 무기한 숨김 없음. | +| D06-21 | 같은 foreground에서 관측 역행/전진 및 같은 정밀도 값 | 역행 조기 종료, monotonic 남은 기간 한도, 표현 정밀도 차이 false positive 없음. 전진 due는 로컬 기준만. | +| D06-22 | 프로세스 종료 중 구간 내부 시계 역행 | 문서화한 검출 한계대로 로컬 정책 수행, 실제 7일 검출을 테스트 결과로 허위 주장하지 않음. | +| D06-23 | route pop/reset/restore 뒤 delayed freshness/dismiss/retry | 오래된 request/generation/episode의 UI·metadata 결과 폐기. 새 화면/데이터 불변. | +| D06-24 | onboarding/recovery/active Preparation Run | reminder가 삽입되거나 일정 시작/복구 확인 흐름을 가로막지 않음. 정상 Home 복귀 뒤 현재 상태만 조회. | +| D06-25 | KO/EN·좁은 화면·200%·screen reader·키보드/탭 | 카드 내용·버튼 잘림 없음, 명확한 접근성 이름/초점, 닫기·백업 진입 가능. password/picker 자동 실행 없음. | +| D06-26 | Android/iOS 합성 사용자 native export 후 resume/relaunch | native 성공/취소/실패와 실제 freshness/snooze read-back 일치. 단위·widget 결과와 실제 기기/OS/artifact 증거 분리. | +| D06-27 | 전체 durable writer 및 schema migration fault inventory | schedule/반복/준비/profile/설정/결과의 content revision·age transaction 일치. backup payload에 metadata/URI/password 없음. | + +## 완료 기준 + +- [ ] 최초 미백업 durable commit부터 720시간을 계산하고 매일 편집하는 negative 회귀가 해결됐다. +- [ ] export cutoff와 그 후 첫 편집을 원자적으로 구분하고 취소/실패/metadata 실패/old retry/새 export/restore 경쟁에서 age가 보존된다. +- [ ] provider 성공 partial receipt와 metadata-only retry를 C01/A02 경계에서 구현했다. provider 중복 호출·비밀번호/목적지 보관·성공 추정이 없다. +- [ ] 실제 schema migration과 file reopen 테스트로 legacy clean/unknown/empty를 구별하고 migration 실패 시 원본 보존을 확인했다. +- [ ] 정상/손상 restore와 reset에서 현 설치 commit·새 identity·old async 방어 및 metadata 제외가 검증됐다. +- [ ] Home/My Data 비차단 카드와 168시간 snooze, 수동 backup 접근, 저장/조회 실패·late UI·lifecycle 처리가 일관된다. +- [ ] timer/clock 역행·전진/invalid interval/재시작 검출 한계를 테스트·문서에 기록했고 remote clock·OS reminder·자동 backup이 없다. +- [ ] KO/EN, 좁은 화면, 200% 텍스트, 접근성 이름/초점과 Android/iOS 소유 QA 실행 증거를 구분해 남겼다. +- [ ] ADR 0035 보충을 적용하고 관련 lifecycle/backup 문서의 표현을 일치시켰다. glossary에 구현 필드를 추가하지 않았다. +- [ ] format/분석/관련 unit·widget/full suite 및 coverage·생성 산출물 정책 검사를 수행하고 실제 명령·SHA·결과를 기록했다. 실패/미실행 검사는 통과로 적지 않았다. + +제품 코드와 자동 검증을 끝내도 native 실행 또는 선행 A09/D02 통합 증거가 없으면 이슈는 OPEN으로 유지하고 구현 완료/검증 대기를 분리한다. 외부 파일을 찾아 삭제하는 기능, store 배포, 운영 기기 데이터 변경은 이 완료 조건이 아니다. + +## grill-with-docs 실제 문답 원문 + +아래는 전담 agent가 root에게 보낸 실제 질문과 root의 실제 답변이다. root가 사용자 대신 제품·기술 결정을 답했다. 각 답변을 받은 뒤 다음 질문을 보냈으며 별도 철회는 없었다. Q1→Q2의 migration/metadata 보충 및 Q3→Q4의 clock 보충도 생략하지 않았다. + +### Q1 — 최초 age와 export 중 편집 + +**Agent 질문** + +> D06 Q1 — 현재 getFreshness는 마지막 변경일(lastDurableDataAt)을 30일 기준으로 사용하고, export는 snapshot을 만든 뒤 일반 편집을 허용합니다. 단순 firstUnexportedAt 한 필드만 추가하면 오래된 변경 A를 포함한 export 중 새 변경 B가 생긴 경우 성공 뒤 A의 오래된 시각을 계속 쓰거나 B를 너무 늦게 계산하는 문제가 남습니다. 권장: ‘현재 설치에서 성공한 export cutoff 이후 가장 먼저 commit된 durable 변경’의 시각/ revision을 원자적으로 추적하고, 단일 export snapshot 경계와 그 뒤 최초 commit 시각을 별도로 기록하는 최소 유계 메타데이터로 처리합니다. export 완료는 cutoff 이하만 해소하고 B의 최초 시각을 남기며, 실패/취소/metadata 기록 실패는 기존 미백업 age를 유지합니다. 매 revision 전체 변경 이력 ledger를 영구 누적하지 않습니다. 메타데이터는 installation-local, portable backup 제외, rollback/no-op/순수 runtime·조회는 age를 바꾸지 않습니다. 30일은 UTC instant 기준 연속 720시간이며 시계가 뒤로 가면 음수 age를 0으로 취급하고, 전진하면 due를 표시하되 실제 30일 경과를 증명한다고 주장하지 않는 로컬시계 기반 안내입니다. 이 정확한 age 및 cutoff 동시 편집 계약으로 확정할까요? 영속 구조는 D04 최신 schema 다음 migration으로 구현하고 A08 WIP schema 3을 지금 변경하지 않겠습니다. + +**Root 답변** + +> Q1 채택합니다. 안내 age는 마지막 성공 export cutoff 이후 최초 durable commit 기준, export snapshot cutoff와 export 중 첫 후속 commit을 원자적으로 구분한 최소 유계 metadata로 유지하세요. export provider 성공과 freshness metadata commit을 구별하며 실패/취소/metadata 실패는 백업완료로 age를 지우지 않습니다. 다만 metadata 실패 시 실제 파일저장 사실도 지우지 말고 A02/C01 partial receipt에 보존하여 DB 재수출/중복provider호출 없이 허용된 metadata 재시도 경계를 정의하세요. gate의 단일export ownership/동시편집 허용 계약과 연동; rollback/no-op/runtime은 age변경없음. 설치 local/backup제외/전체ledger불필요. 30일=UTC720h, 시계역행 0 clamp/전진 due는 안내 기준만이며 신뢰서버시각 도입 없음. 기존 dirty 설치 migration에서 실제 첫미백업시각은 복원할수없으니 lastDurableDataAt을 정확한 최초시각으로 거짓표시하지 말고 unknown/보수적 안내 정책을 다음 질문에서 권고해 주세요. D04/A08 이후 최종schema에 맞춘 migration이고 현재A08소스 수정금지. + +### Q2 — legacy unknown·복원과 metadata-only retry + +**Agent 질문** + +> D06 Q2 — 기존 설치에는 최초 미백업 변경 시각이 없고, 현재 restore는 first/lastDurableDataAt에 원본 backup.cutoff를 넣습니다. 따라서 이를 migration에서 ‘정확히 N일 미백업’으로 바꾸면 허위 이력이 됩니다. 권장: migration 시 현재 revision==성공 export revision이면 clean 유지, 데이터가 전혀 없는 설치에는 안내 없음, 그 외 실제 durable data가 있으면서 정확한 age를 증명할 수 없는 legacy 상태는 age=unknown으로 보존하고 첫 정상 진입부터 비차단 안내를 허용합니다(‘백업하지 않은 변경이 있습니다’, ‘30일 지남’ 숫자 단정 금지). 새 설치의 최초 durable 기록과 성공 restore는 해당 설치 commit 시각부터 30일을 세며, restore는 Never Exported 상태로 시작하고 외부 원본 cutoff·source의 dismiss/export 성공 이력을 가져오지 않습니다. reset은 해당 설치 메타데이터도 지웁니다. metadata 저장 부분실패는 destination/password-free attempt receipt + snapshot revision/store incarnation에 묶어 동일 설치에서 freshness 기록만 재시도하고 provider를 다시 호출하지 않으며, 프로세스 종료 뒤 native 저장 성공 증거를 잃었으면 성공을 추정하지 않습니다. unknown 즉시 안내 예외와 restore 기준은 ADR0035 보충에 명시할 실제 trade-off이고, glossary 용어는 기존 Backup Freshness/Cutoff로 충분합니다. 이 legacy/restore/metadata-retry 정책으로 확정할까요? + +**Root 답변** + +> Q2 채택합니다. 실제 durable data없는 설치는 안내없음; legacy revision과 기록된성공exportrevision이 동일하면 clean, dirty이나첫시점근거없으면 age unknown으로 첫정상진입부터 비차단안내(정확일수/30일단정금지). 새설치최초durable 및 성공restore의안내기준은 현설치commit시각; restore Never Exported, 외부cutoff/dismiss/성공export이력 불승계. reset metadata삭제. metadata-only retry는 C01/A02의같은attempt receipt를확장/재사용하며 병렬새engine금지, store incarnation/snapshotrevision/cutoff 및 당시후속변경first시각과결합. destination/password보존불필요, provider재호출금지. 프로세스재시작후 성공증거없으면 파일성공추정금지. 중요한 추가경계: export완료후metadata재시도전새변경들이생기거나 별도새export가성공해도 옛receipt가최신cutoff/firstdirty/dismiss를되돌리면안됩니다. stale/superseded receipt는적절한완료/obsolete로끝내고 최신age보존. ADR0035 실제tradeoff보충 필요사항을 issue에 exact proposed text로넣되 현재공유ADR은수정하지마세요. 이슈구현시반영합니다. + +### Q3 — Home·My Data·7일 숨김 + +**Agent 질문** + +> D06 Q3 — ADR0035는 dismissible in-app reminder인데 현재는 My Data의 빨간 Text만 있고, Home 노출·닫기·재표시 규칙은 없습니다. 권장: 정상 Home 목록의 비차단 inline 카드에 ‘백업하기’/‘7일 뒤 다시 알림’을 제공하고, My Data는 freshness 상태를 항상 표시하되 동일 reminder 카드의 snooze를 공유합니다. 버튼은 기존 My Data export 흐름으로 이동만 하며 비밀번호/picker를 자동 실행하지 않습니다. snooze는 현재 store/dirty episode에 묶어 168시간 유지하고 단순 앱 재시작·매일 추가 변경으로 해제하지 않습니다. 성공 export가 현재까지 모두 포함하면 episode/snooze를 끝내고, cutoff 뒤 새 변경만 남으면 새 episode로 30일을 계산합니다. 취소/실패/백업화면 열기는 snooze나 age를 자동 갱신하지 않습니다. 안내/dismiss는 durable content revision을 증가시키지 않으며 backup에 포함하지 않습니다. 동일 설치의 Home/My Data 간 재조회·resume·정상 foreground 30일/7일 경계에서 갱신하고 startup/onboarding/Recovery Mode·active Preparation Run 위에는 끼어들지 않습니다. 저장 실패 때 ‘7일 숨김’ 성공을 거짓 표시하지 않고 현재 화면에서는 닫을 수 있게 하되 저장 실패+명시 재시도를 제공합니다. KO/EN, 200% 글자, 좁은 화면, screen reader 라벨/초점, navigation/late generation/중복 탭을 검증하고 OS 알림·자동 백업은 추가하지 않습니다. 이 7일 snooze와 Home/My Data UX 범위로 확정할까요? + +**Root 답변** + +> Q3 채택합니다. Home 정상목록 비차단카드 + MyData freshness항상/동일카드공유, '백업하기'는기존화면이동만. '7일 뒤 다시 알림'=168h, store/dirty episode 결합, 추가변경/재시작으로해제금지. 성공export cutoff이후첫새변경은새episode/새30일, 모두포함시종료; 실패/취소/화면열기는 age/snooze불변. dismiss는contentrevision증가/backup포함없음. startup/onboarding/recovery/active PreparationRun 개입금지. 갱신은기존lifecycle/clock인프라사용, 정상foreground 경계의단발timer재설정 등유계방식으로매초DBpoll없음. 로컬시계역행시 snooze가기간무한연장되지않도록저장start/end와현재관측정책을명시(신뢰시각보증없음), timer/resume재평가 및기기시계변경negative포함. dismiss저장실패는동일mounted화면의임시숨김과persisted성공구별, 재실행시재표시가능을설명/재시도. export와metadata-onlyretry 및lategeneration은새episode/restore후화면에옛결과덮어쓰지않아야합니다. 제시KOEN/200%/좁은화면/screenreader/중복탭검증 채택. 별도OS알림/자동백업없음. + +### Q4 — 시계 역행의 실제 한계와 빈 설치 판정 + +**Agent 질문** + +> D06 Q4 — Q3의 시계 역행 보충을 구현 가능하게 한정하겠습니다. 권장: snooze의 UTC start/end(정상 간격 168h)를 저장하고 now < start, end < start, 간격 >168h, 파싱 불가이면 숨김을 유지하지 않고 due 상태를 재평가합니다. 같은 foreground 세션에서 이미 관측한 UTC보다 시계가 역행하면 기존 snooze를 조기 종료하며, 정상 세션은 최초 관측의 남은 기간(최대168h)을 monotonic 타이머에도 묶어 wall-clock만 반복 뒤로 돌려 무기한 숨기지 못하게 합니다. resume/재시작은 저장된 start/end와 현재 UTC를 다시 평가하고 잘못된 경계는 보수적으로 안내를 허용합니다. 프로세스 종료 중 start/end 내부로만 돌아간 시계 조작은 신뢰시각 없이 정확히 검출할 수 없으므로 ‘실제 7일 보장’은 주장하지 않습니다. 경계 timer는 foreground에서 하나만 유지/취소, 매초 DB polling·서버시각·백그라운드 작업은 없습니다. 또한 최소 durable data는 일정 수만으로 판정하지 않고 사용자가 저장한 onboarding/설정/기본 준비/템플릿/결과 등 전체 백업 범위를 포함하며, 자동 생성된 빈 profile만으로 reminder를 만들지 않습니다. 이 보수적 시계·빈 설치 판정으로 문서를 동결할까요? + +**Root 답변** + +> Q4 채택합니다. timestamp형식/구간불가능/>168h/now Date: Thu, 24 Sep 2026 12:43:31 +0900 Subject: [PATCH 55/59] docs: record exact readback hashes for existing audit issues --- handoff/audit-2026-09-23-progress.md | 2 + .../legacy-issue-readback-20260924.json | 119 ++++++++++++++++++ plans/audit-2026-09-23/state.json | 42 ++++--- 3 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 plans/audit-2026-09-23/legacy-issue-readback-20260924.json diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 7739e575..dc737f1d 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -140,3 +140,5 @@ O01#621 실제3문답·파일inventory/모바일실행·15개검증 기준 게 O02#622 실제3문답·데이터흐름11·검증22개 기준 게시/readback완료,36/64(미생성28). 공개URL/스토어 현재상태는 미조회 unknown이며 외부정책 변경 없음. 새 D06 전담 /root/grill_d06 문답 착수. A08 중간 focused52/migration5 통과, 새초안 stale saveReceipt 경계와 watch rollback 검증 보완 중. Q4 내부 recurring create의 조용한 중복성공을 typed conflict로 교체하고 미사용 save 우회 경로 제거 채택; 제품 중복성공은 aggregate durable receipt 경계로 한정. 최종 sourcefreeze/전체검증 전 제품 WIP 커밋 금지. D06#623 전담실제4문답·27개검증·10개AC 게시/readback완료,37/64(미생성27). 첫미백업commit/cutoff중편집/metadata-onlyretry/legacyunknown/restore시점/7일숨김·시계한계 확정. ADR0035보충안은이슈에준비,공유ADR아직미적용. D07 새전담 /root/grill_d07 착수. A08 추가root검토3건(create default+baseline혼합snapshot, 모달oldowner응답, place/template/default의recurring root receipt무효화)을담당이실제경계로확인해수정중. focused52/migration5외최종회귀·UI·full아직미완료. A09 구현preflight를da7e6e34에보존, A08freeze후이어갈예정. + +누락되어있던기존14개 issue_body_sha256을GitHub read-only재조회해현재local본문과모두exact일치확인후state에기록했다. 원격본문수정없음; legacy-issue-readback-20260924.json 보존. A08 추가집중45/UI+연관통합26 통과보고및로그확인. root는 /tmp/a08-ui KOEN200% rollback/deliverypending4PNG직접확인(새receipt본문/버튼접근가능,ENunderlay단계라벨/필드말줄임은U08전체레이아웃범위). 최종analyze/full/sourcefreeze는아직미완료,마지막원격검증제품C01과혼동금지. diff --git a/plans/audit-2026-09-23/legacy-issue-readback-20260924.json b/plans/audit-2026-09-23/legacy-issue-readback-20260924.json new file mode 100644 index 00000000..69490ae1 --- /dev/null +++ b/plans/audit-2026-09-23/legacy-issue-readback-20260924.json @@ -0,0 +1,119 @@ +{ + "checked_at": "2026-09-24", + "operation": "read_only", + "scope": "Previously published items without a recorded body hash; exact remote body versus local file", + "issues": [ + { + "audit_id": "A03", + "number": 586, + "state": "OPEN", + "exact_match": true, + "sha256": "b71cdb49ee31482c29244505f041bd6ec18084401d8a3b7aa68e18db9a90984c", + "url": "https://github.com/DevKor-github/OnTime-front/issues/586" + }, + { + "audit_id": "A04", + "number": 587, + "state": "OPEN", + "exact_match": true, + "sha256": "d7aad74fd3e48468cd0d9ffde95cb531b48e1bc2ee616d74eca32a766ba5a603", + "url": "https://github.com/DevKor-github/OnTime-front/issues/587" + }, + { + "audit_id": "A05", + "number": 588, + "state": "OPEN", + "exact_match": true, + "sha256": "b8bbdf6aaa69075453f3f55cb327594e2cf2ca1c4e3acf703e04cfc6b3f0fa67", + "url": "https://github.com/DevKor-github/OnTime-front/issues/588" + }, + { + "audit_id": "A11", + "number": 589, + "state": "OPEN", + "exact_match": true, + "sha256": "451d17d7e378a2673b098c1180f4bf8aa98023bef1a672544a7dafbd2515ce41", + "url": "https://github.com/DevKor-github/OnTime-front/issues/589" + }, + { + "audit_id": "A12", + "number": 591, + "state": "OPEN", + "exact_match": true, + "sha256": "8b2c2dc7fe7cdb75662208d589727a4dcbbb728da87095b700e454f660a56f54", + "url": "https://github.com/DevKor-github/OnTime-front/issues/591" + }, + { + "audit_id": "A13", + "number": 592, + "state": "OPEN", + "exact_match": true, + "sha256": "baab5f33ce7bd44c3851068bcd8a10f003406fac96ea4d627f79d76352278f97", + "url": "https://github.com/DevKor-github/OnTime-front/issues/592" + }, + { + "audit_id": "A15", + "number": 594, + "state": "OPEN", + "exact_match": true, + "sha256": "ea30e4068c598fe9632e8a56e08a5bffc8eb661079e8eb2d74c6ed5acefba569", + "url": "https://github.com/DevKor-github/OnTime-front/issues/594" + }, + { + "audit_id": "D03", + "number": 595, + "state": "OPEN", + "exact_match": true, + "sha256": "8c7ae3ed41f7d9f9c9d8622a7c62e2c5ae21486ef212b63d0ba34a5604647fbc", + "url": "https://github.com/DevKor-github/OnTime-front/issues/595" + }, + { + "audit_id": "A08", + "number": 601, + "state": "OPEN", + "exact_match": true, + "sha256": "bd5f1b77a400c56f5fb1656412201f72d8288a0a63ed79a699a16f45e7cc1c76", + "url": "https://github.com/DevKor-github/OnTime-front/issues/601" + }, + { + "audit_id": "A09", + "number": 602, + "state": "OPEN", + "exact_match": true, + "sha256": "97c77e8e911aa351002e7979674507f4586ef0fabd96ad3cf91343e902f2638a", + "url": "https://github.com/DevKor-github/OnTime-front/issues/602" + }, + { + "audit_id": "D01", + "number": 603, + "state": "OPEN", + "exact_match": true, + "sha256": "f75e396b4b7da225ec721ee745f11d95ebf264e0debd3d3ca82cbd33894b8d21", + "url": "https://github.com/DevKor-github/OnTime-front/issues/603" + }, + { + "audit_id": "D02", + "number": 604, + "state": "OPEN", + "exact_match": true, + "sha256": "a4c5925fb022668a4ad869ac7f6faa3be8d3068de42ac69235132de9aba3af02", + "url": "https://github.com/DevKor-github/OnTime-front/issues/604" + }, + { + "audit_id": "D04", + "number": 605, + "state": "OPEN", + "exact_match": true, + "sha256": "6ab517bf1b1a2b59c81d52306941df653be2f81d01efaea44da3af0036575247", + "url": "https://github.com/DevKor-github/OnTime-front/issues/605" + }, + { + "audit_id": "D05", + "number": 606, + "state": "OPEN", + "exact_match": true, + "sha256": "15cea8d115b001831a7fc9deb65e68fb9934879c2a361b50aa594fb06c4ad2ef", + "url": "https://github.com/DevKor-github/OnTime-front/issues/606" + } + ] +} diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 86aa7576..16ec83e0 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -105,7 +105,8 @@ ], "sequence": 3, "readback_verified": true, - "body_sha256": "b71cdb49ee31482c29244505f041bd6ec18084401d8a3b7aa68e18db9a90984c" + "body_sha256": "b71cdb49ee31482c29244505f041bd6ec18084401d8a3b7aa68e18db9a90984c", + "issue_body_sha256": "b71cdb49ee31482c29244505f041bd6ec18084401d8a3b7aa68e18db9a90984c" }, { "audit_id": "A04", @@ -140,7 +141,8 @@ ], "sequence": 4, "readback_verified": true, - "body_sha256": "d7aad74fd3e48468cd0d9ffde95cb531b48e1bc2ee616d74eca32a766ba5a603" + "body_sha256": "d7aad74fd3e48468cd0d9ffde95cb531b48e1bc2ee616d74eca32a766ba5a603", + "issue_body_sha256": "d7aad74fd3e48468cd0d9ffde95cb531b48e1bc2ee616d74eca32a766ba5a603" }, { "audit_id": "A05", @@ -176,7 +178,8 @@ ], "sequence": 5, "readback_verified": true, - "body_sha256": "b8bbdf6aaa69075453f3f55cb327594e2cf2ca1c4e3acf703e04cfc6b3f0fa67" + "body_sha256": "b8bbdf6aaa69075453f3f55cb327594e2cf2ca1c4e3acf703e04cfc6b3f0fa67", + "issue_body_sha256": "b8bbdf6aaa69075453f3f55cb327594e2cf2ca1c4e3acf703e04cfc6b3f0fa67" }, { "audit_id": "A11", @@ -214,7 +217,8 @@ ], "sequence": 6, "readback_verified": true, - "body_sha256": "451d17d7e378a2673b098c1180f4bf8aa98023bef1a672544a7dafbd2515ce41" + "body_sha256": "451d17d7e378a2673b098c1180f4bf8aa98023bef1a672544a7dafbd2515ce41", + "issue_body_sha256": "451d17d7e378a2673b098c1180f4bf8aa98023bef1a672544a7dafbd2515ce41" }, { "audit_id": "A12", @@ -245,7 +249,8 @@ ], "sequence": 7, "readback_verified": true, - "body_sha256": "8b2c2dc7fe7cdb75662208d589727a4dcbbb728da87095b700e454f660a56f54" + "body_sha256": "8b2c2dc7fe7cdb75662208d589727a4dcbbb728da87095b700e454f660a56f54", + "issue_body_sha256": "8b2c2dc7fe7cdb75662208d589727a4dcbbb728da87095b700e454f660a56f54" }, { "audit_id": "A13", @@ -277,7 +282,8 @@ "sequence": 8, "readback_verified": true, "body_sha256": "baab5f33ce7bd44c3851068bcd8a10f003406fac96ea4d627f79d76352278f97", - "implementation_followup_commit": "edfff97c4806fb2d154c324edf8965b1feb218e0" + "implementation_followup_commit": "edfff97c4806fb2d154c324edf8965b1feb218e0", + "issue_body_sha256": "baab5f33ce7bd44c3851068bcd8a10f003406fac96ea4d627f79d76352278f97" }, { "audit_id": "A14", @@ -345,7 +351,8 @@ ], "sequence": 10, "readback_verified": true, - "body_sha256": "ea30e4068c598fe9632e8a56e08a5bffc8eb661079e8eb2d74c6ed5acefba569" + "body_sha256": "ea30e4068c598fe9632e8a56e08a5bffc8eb661079e8eb2d74c6ed5acefba569", + "issue_body_sha256": "ea30e4068c598fe9632e8a56e08a5bffc8eb661079e8eb2d74c6ed5acefba569" }, { "audit_id": "D03", @@ -381,7 +388,8 @@ "progress_file": "plans/audit-2026-09-23/d03-journal-progress.json", "validation_file": "plans/audit-2026-09-23/d03-final-validation.json", "integration_validation_file": "plans/audit-2026-09-23/d03-main-integration-validation.json", - "ci_validation_file": "plans/audit-2026-09-23/d03-ci-validation.json" + "ci_validation_file": "plans/audit-2026-09-23/d03-ci-validation.json", + "issue_body_sha256": "8c7ae3ed41f7d9f9c9d8622a7c62e2c5ae21486ef212b63d0ba34a5604647fbc" }, { "audit_id": "A06", @@ -510,7 +518,8 @@ "readback_verified": true, "body_sha256": "bd5f1b77a400c56f5fb1656412201f72d8288a0a63ed79a699a16f45e7cc1c76", "implementation_agent": "/root/implement_a08", - "implementation_plan": "plans/audit-2026-09-23/a08-implementation-preflight.md" + "implementation_plan": "plans/audit-2026-09-23/a08-implementation-preflight.md", + "issue_body_sha256": "bd5f1b77a400c56f5fb1656412201f72d8288a0a63ed79a699a16f45e7cc1c76" }, { "audit_id": "A09", @@ -534,7 +543,8 @@ "blockers": [], "sequence": 16, "readback_verified": true, - "body_sha256": "97c77e8e911aa351002e7979674507f4586ef0fabd96ad3cf91343e902f2638a" + "body_sha256": "97c77e8e911aa351002e7979674507f4586ef0fabd96ad3cf91343e902f2638a", + "issue_body_sha256": "97c77e8e911aa351002e7979674507f4586ef0fabd96ad3cf91343e902f2638a" }, { "audit_id": "D01", @@ -558,7 +568,8 @@ "blockers": [], "sequence": 17, "readback_verified": true, - "body_sha256": "f75e396b4b7da225ec721ee745f11d95ebf264e0debd3d3ca82cbd33894b8d21" + "body_sha256": "f75e396b4b7da225ec721ee745f11d95ebf264e0debd3d3ca82cbd33894b8d21", + "issue_body_sha256": "f75e396b4b7da225ec721ee745f11d95ebf264e0debd3d3ca82cbd33894b8d21" }, { "audit_id": "D02", @@ -582,7 +593,8 @@ "blockers": [], "sequence": 18, "readback_verified": true, - "body_sha256": "a4c5925fb022668a4ad869ac7f6faa3be8d3068de42ac69235132de9aba3af02" + "body_sha256": "a4c5925fb022668a4ad869ac7f6faa3be8d3068de42ac69235132de9aba3af02", + "issue_body_sha256": "a4c5925fb022668a4ad869ac7f6faa3be8d3068de42ac69235132de9aba3af02" }, { "audit_id": "D04", @@ -606,7 +618,8 @@ "blockers": [], "sequence": 19, "readback_verified": true, - "body_sha256": "6ab517bf1b1a2b59c81d52306941df653be2f81d01efaea44da3af0036575247" + "body_sha256": "6ab517bf1b1a2b59c81d52306941df653be2f81d01efaea44da3af0036575247", + "issue_body_sha256": "6ab517bf1b1a2b59c81d52306941df653be2f81d01efaea44da3af0036575247" }, { "audit_id": "D05", @@ -630,7 +643,8 @@ "blockers": [], "sequence": 20, "readback_verified": true, - "body_sha256": "15cea8d115b001831a7fc9deb65e68fb9934879c2a361b50aa594fb06c4ad2ef" + "body_sha256": "15cea8d115b001831a7fc9deb65e68fb9934879c2a361b50aa594fb06c4ad2ef", + "issue_body_sha256": "15cea8d115b001831a7fc9deb65e68fb9934879c2a361b50aa594fb06c4ad2ef" }, { "audit_id": "A10", From fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 12:44:52 +0900 Subject: [PATCH 56/59] docs: define template backup timestamp preservation --- handoff/audit-2026-09-23-progress.md | 2 + plans/audit-2026-09-23/README.md | 3 +- plans/audit-2026-09-23/issues/D07.md | 162 +++++++++++++++++++++++++++ plans/audit-2026-09-23/state.json | 12 +- 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/D07.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index dc737f1d..e6e8bf59 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -142,3 +142,5 @@ O02#622 실제3문답·데이터흐름11·검증22개 기준 게시/readback완 D06#623 전담실제4문답·27개검증·10개AC 게시/readback완료,37/64(미생성27). 첫미백업commit/cutoff중편집/metadata-onlyretry/legacyunknown/restore시점/7일숨김·시계한계 확정. ADR0035보충안은이슈에준비,공유ADR아직미적용. D07 새전담 /root/grill_d07 착수. A08 추가root검토3건(create default+baseline혼합snapshot, 모달oldowner응답, place/template/default의recurring root receipt무효화)을담당이실제경계로확인해수정중. focused52/migration5외최종회귀·UI·full아직미완료. A09 구현preflight를da7e6e34에보존, A08freeze후이어갈예정. 누락되어있던기존14개 issue_body_sha256을GitHub read-only재조회해현재local본문과모두exact일치확인후state에기록했다. 원격본문수정없음; legacy-issue-readback-20260924.json 보존. A08 추가집중45/UI+연관통합26 통과보고및로그확인. root는 /tmp/a08-ui KOEN200% rollback/deliverypending4PNG직접확인(새receipt본문/버튼접근가능,ENunderlay단계라벨/필드말줄임은U08전체레이아웃범위). 최종analyze/full/sourcefreeze는아직미완료,마지막원격검증제품C01과혼동금지. + +D07#624 실제3문답+Drift버전정정·18검증·10AC 게시/readback완료,38/64(미생성26). template restore전용createdAt/updatedAt보존·template-onlyUTCexport·id동률정렬·legacyoffsetless한계·nonzero subsecond사전거부/공유validator·A09/D02/양플랫폼통합검증확정. U04 새전담 /root/grill_u04 착수. A08최종analyzer정리중이고sourcefreeze대기. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index df543fd5..b06fbfb0 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -53,8 +53,9 @@ | O01 | [#621](https://github.com/DevKor-github/OnTime-front/issues/621) | 실제 3문답·현행/역사 문서 inventory·두 모바일 재현·15개 검증 행렬 확정 | | O02 | [#622](https://github.com/DevKor-github/OnTime-front/issues/622) | 최종 바이너리와 실제 배포 범위에 맞춘 개인정보 정책·스토어 신고 및 공개 검증 | | D06 | [#623](https://github.com/DevKor-github/OnTime-front/issues/623) | 실제 4문답·최초 미백업 age/동시 export/7일 숨김·27개 검증 기준 확정 | +| D07 | [#624](https://github.com/DevKor-github/OnTime-front/issues/624) | 실제 3문답·템플릿 두 시각/UTC·legacy/안정 정렬·18개 검증 기준 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **37/64개**를 게시하고 원격 본문 동일성을 확인했으며 **27개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했다. C01은4529d9f8로 구현해 최종 원격959 tests·88.72%·APK를 통과했고 A08 구현 중이다. A08의 현재 부분 검증은 집중52개와 역사 migration5개이며 최종 전체 검증은 남아 있다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **38/64개**를 게시하고 원격 본문 동일성을 확인했으며 **26개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했다. C01은4529d9f8로 구현해 최종 원격959 tests·88.72%·APK를 통과했고 A08 구현 중이다. A08의 현재 부분 검증은 집중52개와 역사 migration5개이며 최종 전체 검증은 남아 있다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/D07.md b/plans/audit-2026-09-23/issues/D07.md new file mode 100644 index 00000000..6cd8850a --- /dev/null +++ b/plans/audit-2026-09-23/issues/D07.md @@ -0,0 +1,162 @@ +# [P2][D07] 백업 복원에서 템플릿 생성·수정 시각과 생성일 정렬을 보존한다 + +상태: 전담 3문답 및 보충·근거 정정 완료, 설계 본문 동결. 구현·테스트 실행 전이며 아래 검증은 수용 계획이다. +전담: `/root/grill_d07`; 적용 skill: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +조사 고정 commit: `a4dfbe426b33815c96386fe3cb6af22186daf98a`. 동시 진행 중인 A08 schema 3 작업 파일·fixture는 이 고정 소스의 증거로 사용하지 않았다. + +## 문제와 사용자 영향 + +백업은 템플릿의 `createdAt`과 `updatedAt`을 별도로 내보내고 읽지만, 복원은 일반 편집용 DAO에 `now: template.updatedAt`만 전달한다. 전체 교체로 기존 행이 삭제된 뒤 DAO는 `createdAt = existing?.createdAt ?? now`를 실행하므로, 원래 생성 시각이 수정 시각으로 덮인다. 이는 값의 누락이며 단순한 표시 차이가 아니다. 템플릿 목록이 `createdAt ASC`로 정렬되므로 오래된 템플릿을 나중에 편집한 사용자는 복원 후 목록 순서까지 달라질 수 있다. + +예: A는 1일 생성·4일 수정, B는 2일 생성·3일 수정이다. 백업 전 A→B였던 목록이 현재 복원 경로에서는 B→A로 바뀐다. 템플릿 수·이름·준비 단계가 맞아도 이 복원은 원본 메타데이터를 보존하지 못한다. P2인 이유는 백업 자체를 열지 못하거나 일정 본문을 잃는 문제보다 우선순위는 낮지만, 정상 백업의 반복 가능한 자료 손실과 사용자 목록 순서 변경이기 때문이다. + +추가로 현재 DB에서 읽은 timestamp는 local DateTime이고 그대로 ISO 문자열을 만들기 때문에 offset이 없는 백업이 생성될 수 있다. D07은 새 템플릿 timestamp 표현을 명확히 하면서 기존 offset 없는 백업의 복구 불가능한 source-zone 모호성을 숨기지 않는다. + +## 고정 소스와 확인 범위 + +| 근거 | 확인한 사실 | +|---|---| +| [복원 materialization](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/lib/core/backup/backup_service.dart#L264-L270) | `put`에 `updatedAt`만 `now`로 넘긴다. 같은 transaction 앞에서 durable 전체 삭제가 실행되어 이전 createdAt을 사용할 수 없다. | +| [일반 DAO 저장](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/lib/data/daos/preparation_template_dao.dart#L30-L61) | 일반 편집은 기존 createdAt을 보존하고 updatedAt을 now로 쓴다. 새 행은 두 값을 now로 쓴다. 단계 삭제·재삽입은 DAO transaction 안이다. | +| [목록 정렬](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/lib/data/daos/preparation_template_dao.dart#L16-L20) | createdAt 오름차순만 명시하고 동률 tie-breaker는 없다. DB의 우연한 행 순서는 기존 제품 계약이 아니다. | +| [백업 직렬화](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/lib/core/backup/backup_service.dart#L402-L411), [역직렬화](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/lib/core/backup/backup_service.dart#L482-L493) | 두 timestamp는 wire에 이미 존재하고 둘 다 parse한다. 새 field/format이 필요한 누락이 아니라 저장 전달 누락이다. export의 `toIso8601String()` 앞에 UTC 변환이 없다. | +| [테이블](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/lib/data/tables/preparation_template_table.dart#L4-L11), [repository](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/lib/data/repositories/preparation_template_repository_impl.dart#L28-L52) | 두 DateTime 열은 이미 존재한다. 일반 create/edit는 DateTime.now를 넘긴다. D07만으로 새 timestamp 열이나 migration을 만들 이유가 없다. | +| [잠긴 dependency](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/pubspec.lock#L267-L274) | 실제 잠긴 Drift는 **2.31.0**이다. 조사 도중 다른 설치 cache 2.34.3을 잘못 지칭한 원문과 정정은 아래에 모두 보존했다. | +| Drift 2.31.0의 설치된 `lib/src/runtime/types/mapping.dart:72,174` | 기본 DateTime SQL 매핑은 `millisecondsSinceEpoch ~/ 1000`으로 epoch 초를 저장하고 `DateTime.fromMillisecondsSinceEpoch(unixSeconds * 1000)`으로 local DateTime을 읽는다. 이 저장소에서 text timestamp 옵션은 확인되지 않았다. dependency 소스를 읽은 근거이며 Flutter 실행 결과가 아니다. | +| [기존 기본 round-trip 테스트](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/test/core/backup/backup_service_test.dart#L210-L240) | profile note/score 및 schedule ID를 검사한다. template의 서로 다른 생성·수정 시각을 검사하는 근거가 아니다. | + +## 기존 용어와 계약 + +[CONTEXT](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/CONTEXT.md)의 **OnTime Backup**, **Backup Restore**, **Durable OnTime Data**, **Restore Preview**, **Backup Cutoff**를 그대로 사용한다. template timestamp는 백업 실행 시각이나 Cutoff가 아니다. 두 timestamp는 템플릿 메타데이터이며 Schedule의 civil date/time과도 구분한다. + +- [ADR 0013](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/docs/adr/0013-replace-durable-data-on-backup-restore.md): 복원은 자료 전체 교체이며 merge가 아니다. +- [ADR 0026](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/docs/adr/0026-use-a-cross-platform-backup-format.md): 플랫폼 중립 자료를 staging에 적재·검증하고 표현 불가 자료를 활성 변경 전에 거절한다. +- [ADR 0027](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/docs/adr/0027-export-a-point-in-time-backup-snapshot.md): 하나의 committed snapshot을 내보낸다. 생성일은 snapshot의 행에서 가져오고 export 완료 시간으로 재작성하지 않는다. +- [ADR 0028](https://github.com/DevKor-github/OnTime-front/blob/a4dfbe426b33815c96386fe3cb6af22186daf98a/docs/adr/0028-require-a-verified-restore-preview.md): 검증된 후보를 preview하고 별도 최종 확인 후에 교체한다. + +새 제품 용어와 새 ADR은 만들지 않는다. 기존 두 field의 자료 보존 및 기존 지원 표현의 명료화이며, 높은 전환 비용을 수반하는 새 저장 체계 선택이 아니다. 구현 시 관련 백업 문서의 정확한 표현만 정비한다. + +## 확정 설계 + +### 일반 편집과 복원 저장의 경계 + +1. 일반 create/edit의 `put(now)` 계약을 유지한다. 새 템플릿은 생성·수정 시각이 동일하고, 기존 템플릿 수정은 createdAt을 유지하며 updatedAt만 명시적인 저장 시각으로 바꾼다. 일반 화면이 임의 생성 시각을 덮을 수 있는 범용 두 timestamp upsert로 바꾸지 않는다. +2. **검증된 restore 전용 저장 경로**는 createdAt과 updatedAt을 각각 필수로 받아 저장한다. 실제 API 이름은 선행 A09/D02 materializer와 맞춘다. optional parameter의 기본값으로 `now`, Cutoff, 다른 timestamp를 사용하지 않는다. +3. 전체 교체에서는 백업의 두 값이 권위다. 대상 DB에 동일 ID의 다른 템플릿이 있더라도 대상 생성일을 섞지 않는다. stage에 적재할 때와 최종 active read-back이 같은 값을 보여야 한다. +4. template 행과 모든 step의 저장은 전체 복원 transaction 또는 검증된 candidate store 적재 경계 안에 둔다. 실패한 두 번째 template/step이 부분 교체 결과를 성공으로 남기지 않는다. 일반 repository의 다른 원자성 문제를 이 변경으로 해결했다고 확대하지 않는다. +5. 과거 잘못된 restore로 이미 손실된 createdAt은 updatedAt·UUID·다른 항목 순서로 추정해 복구하지 않는다. 정상 원본 백업을 다시 복원하는 것은 사용자의 별도 명시적 전체 교체 선택이다. +6. `updatedAt < createdAt`은 기기 시계 역행으로 가능하다. D07에서 단조 시간이나 미래 시간 금지 규칙을 새로 강제하지 않는다. 두 값의 문법·지원 범위·저장 가능성은 각각 검증한다. + +### UTC instant, legacy 표현과 정밀도 + +- 새 export의 **template.createdAt/updatedAt 두 field만** UTC ISO-8601 `Z`로 직렬화한다. Schedule civil 표현이나 모든 날짜를 일괄 UTC 전환하지 않는다. payload format 1/2 reader와 기존 crypto suite를 유지하고 D07 때문에 format 번호를 올리지 않는다. +- 명시 offset 또는 `Z`가 있는 지원 입력은 같은 UTC instant를 저장한다. 문자열 spelling, offset 표시 및 DateTime의 `isUtc` 플래그 일치는 자료 보존 판정 기준이 아니다. +- offset 없는 legacy template timestamp는 기존 parser의 현재 기기 local 해석 호환을 유지한다. 원래 source zone이 payload에 없으므로 다른 zone에서 원래 instant를 정확하게 복원한다고 보장할 수 없다. Cutoff, Schedule Time Zone, 실행 기기 또는 임의 fixture 값을 원래 source zone으로 추정하지 않는다. 새 export가 과거 모호성을 소급 해결했다는 표현을 금지한다. +- DB의 현재 epoch **초** 해상도를 유지한다. 정상 앱의 실제 저장 행→export→restore round-trip은 그 source row의 epoch instant로 동등성을 검사한다. DateTime 생성 직후의 아직 저장되지 않은 subsecond 값과 DB read-back을 혼동하지 않는다. +- `.000`과 정수 초에 해당하는 offset 정규화는 허용한다. **nonzero subsecond 입력은 D05와 공유하는 표현 가능성 validator에서 파괴적 apply 전에 명시적으로 거절**한다. 조용히 자른 뒤 정확한 보존이라고 보고하지 않는다. 과거 정상 앱 export가 실제 subsecond를 생성했다는 증거가 나오면 호환 결정을 재검토하고 기록한 후 구현한다. +- 음수 epoch와 지원 year 범위 경계도 검사한다. 전체 초로 표현 가능한 과거 시각을 단순히 음수라는 이유로 거절하지 않고, 음수 fractional 값을 `~/ 1000`으로 0 방향 절삭해 몰래 정규화하지 않는다. D05의 strict calendar/range parser를 재사용하고 동일 내용의 별도 validator를 만들지 않는다. D05보다 먼저 구현하면 공통 경계에 필요한 최소 검증을 제공한다. + +### 정렬 + +`getAll()`의 정렬을 **createdAt ASC, id ASC**로 명시한다. 다른 createdAt 사이의 기존 순서는 유지하고, 동일 createdAt의 순서는 ID로 안정화한다. DB insertion/rowid 순서나 updatedAt을 tie-breaker로 간주하지 않는다. 동일 timestamp에 ID 역순으로 삽입해도 export/restore/독립 reopen 이후 같은 순서여야 한다. UI에 수동 정렬 또는 새 정렬 선택 기능을 추가하지 않는다. + +## 의존성과 범위 + +| 연결 | D07 책임과 경계 | +|---|---| +| A09 [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | 정상 복원의 실제 staging과 active 교체에 같은 template materializer/검증을 사용한다. 기존 메모리 candidate 테스트만으로 staging을 완료 처리하지 않는다. | +| D02 [#604](https://github.com/DevKor-github/OnTime-front/issues/604) | 손상 DB Recovery의 새 candidate store에서도 두 timestamp와 steps를 동일하게 적재하고 active pair 선택 후 재열기로 확인한다. DB/key/journal 교체 엔진은 D02 소유다. | +| D05 [#606](https://github.com/DevKor-github/OnTime-front/issues/606) | strict 문법·range·자료 표현·중복 ID·preparation graph 검증을 공유한다. nonzero subsecond는 명시 거절한다. 포맷 호환을 테스트하기 전에 새 이름/기간/시간 단조 규칙으로 기존 합법 자료를 배제하지 않는다. | +| D04 [#605](https://github.com/DevKor-github/OnTime-front/issues/605) | 실제 역사 DB 및 migration 후 timestamp 보존과 native 암호화 store 증거를 연결한다. D07 자체를 이유로 별도 schema migration을 만들지 않는다. | +| C01 [#600](https://github.com/DevKor-github/OnTime-front/issues/600) / D01 [#603](https://github.com/DevKor-github/OnTime-front/issues/603) | typed 검증 실패·취소·부분 후속 작업 결과를 유지한다. DB commit 뒤 delivery 실패는 template 복원을 다시 실행하는 이유가 아니다. 내부 timestamp/사용자 내용이 raw 예외로 노출되지 않는다. | +| A08 [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 구현 착수 시 최종 schema 3·store/aggregate identity 계약을 확인한다. installation-local receipt/identity는 portable backup에 넣지 않으며, template 복원으로 의존 일정 버전 처리 계약을 우회하지 않는다. | +| A02 [#584](https://github.com/DevKor-github/OnTime-front/issues/584), A03 [#586](https://github.com/DevKor-github/OnTime-front/issues/586), T01 [#615](https://github.com/DevKor-github/OnTime-front/issues/615), T02 [#616](https://github.com/DevKor-github/OnTime-front/issues/616) | Android→iOS 및 iOS→Android 실제 파일 이동 증거에 template 두 시각/순서 비교를 포함한다. 기존 검증을 재사용하거나 관측을 추가하고 picker의 전체 매트릭스를 D07에서 복제하지 않는다. | +| C03 | N+1/batch 성능 개선은 별도 범위다. timestamp 보존의 필수 조건으로 전면 DAO 리팩터링을 묶지 않는다. | + +범위 제외: 서버·로그인·동기화, 임의 과거 timestamp 추정 복구, microsecond DB/schema 전환, 새 백업 format/crypto, 전체 날짜 UTC 전환, 수동 템플릿 순서 기능, 일반 템플릿 편집의 전면 원자성 개편, 삭제된 template 복원 정책 신설, UI의 날짜 노출 기능, 새 restore coordinator. + +## 의미 있는 검증 행렬 + +모두 **실행할 계획**이다. 호출 인자 mock 검증만으로 통과시키지 않는다. 공통 정상 fixture는 A(1일 생성·4일 수정), B(2일 생성·3일 수정), 같은 생성 timestamp의 역순 ID 두 개, 다단계 preparation을 포함한다. 읽어 온 source row를 기준으로 ID·이름·두 epoch instant·step ID/이름/분/position/nextId와 전체 목록 순서를 비교한다. 암호화 bytes는 랜덤 salt 때문에 같을 필요가 없으며 재export의 검증된 template 내용으로 비교한다. + +| 번호 | 시나리오 | 필요한 결과·증거 | +|---|---|---| +| 1 | 현재 결함 재현 | 수정 전 실제 restore에서 A.createdAt이 A.updatedAt으로 바뀌고 A/B 순서가 역전하는 실패를 기록한다. 이름/count만 확인하는 성공은 불충분하다. | +| 2 | 실제 DB→암호화 export→preview→교체→독립 reopen→재export | 모든 template field와 preparation 순서/참조가 source row와 같고 A→B 순서가 유지된다. preview metadata count도 실제 read-back과 맞는다. | +| 3 | format 1/2 | 각 지원 형식의 정상 앱 생성 자료/역사 fixture와 두 timestamp를 검사한다. format 2 recurring 자료를 누락해 format 1 통과로 대체하지 않는다. | +| 4 | 대상 동일 ID의 다른 생성·수정 시각 | 전체 교체 결과는 백업의 두 값이다. 대상 메타데이터가 merge되지 않는다. | +| 5 | 일반 create/edit | 새 행 두 값이 명시 now, 편집 후 createdAt 불변·updatedAt 갱신. restore 전용 인자를 일반 편집에 노출하지 않는다. | +| 6 | updatedAt < createdAt, 두 값 동일 | 지원 범위의 시계 역행과 동일 시간 자료를 그대로 수용한다. 잘못된 단조 검증이나 자동 now 교체가 없다. | +| 7 | createdAt 동률·ID 역순 삽입 | createdAt ASC/id ASC가 독립 reopen·복원·재export에도 같고 updatedAt의 변화로 tie 순서가 바뀌지 않는다. | +| 8 | UTC `Z`와 명시 ±offset | 같은 instant를 source/destination zone이 다른 독립 실행 환경에서 보존한다. 새 template export 두 field는 Z이며 Schedule civil payload는 기존 의미를 유지한다. | +| 9 | offset 없는 legacy | 실제 legacy wire 표현을 그대로 fixture로 사용한다. 기존 destination-local 해석을 유지하며 source-zone 정보가 없는 한계를 기록한다. 임의 zone을 추가한 fixture로 교차 zone 보존을 주장하지 않는다. | +| 10 | `.000`, nonzero milliseconds/microseconds, 음수 epoch fractional | 정수 초 지원 값은 보존, nonzero subsecond는 apply 전에 명시 거절. 값 절삭·0 방향 반올림·자료 일부 적용이 없다. | +| 11 | 음수 정수 epoch, 지원 날짜 범위 경계·overflow 날짜·타입 오류 | 지원 정수 초의 과거 시각을 보존하고 불가 표현은 공통 validator가 거절한다. 원본 active store/key/runtime이 유지된다. | +| 12 | 두 번째 template insert 또는 step insert 실제 DB 실패 | 전체 활성 자료·두 시각·준비 단계·revision이 이전 상태로 남거나 D02의 미활성 candidate만 실패한다. 첫 template만 바뀐 성공 없음. 실제 DB constraint/fault 결과를 확인한다. | +| 13 | 잘못된 암호·잘림·중복 ID·잘못된 graph·취소 | 인증/검증/사용자 확인 전 template 저장 없음, 현재 자료 보존. D05 공통 경로 및 기존 crypto 실패 테스트를 재사용한다. | +| 14 | 복원 후 사용자 편집 및 다시 백업 | 복원 createdAt은 그대로, 새 updatedAt만 바뀌며 다시 복원해도 원래 createdAt을 유지한다. 이전 버그의 재발 경계를 검사한다. | +| 15 | A09 정상 staging / D02 손상 Recovery | 같은 materializer의 staging read-back과 최종 독립 active reopen에 같은 두 instant/steps/순서가 남는다. 정상 DB가 없는 Recovery도 검증을 우회하지 않는다. | +| 16 | DB commit 뒤 delivery 후속 작업 실패와 재시도 | committed template timestamp를 다시 now로 쓰거나 백업을 재apply하지 않는다. C01 receipt 기준 후속 작업만 재시도한다. | +| 17 | Android→iOS / iOS→Android 실제 파일 이동 | 각 방향의 source artifact/설치·OS·파일 식별과 template read-back을 기록한다. native SQLCipher 및 실제 기기 증거를 host SQLite 결과와 구분한다. | +| 18 | 이미 손실된 createdAt=updatedAt 자료 | 기존 값을 임의 추정 변경하지 않는다. 이 수정이 과거 원본 생성일을 복구했다고 보고하지 않는다. | + +## 수용 기준과 완료 보고 + +- [ ] 실제 실패하는 최소 회귀가 기존 복원의 createdAt 손실과 정렬 역전을 입증한다. +- [ ] 일반 edit/create와 검증된 restore 저장 경계가 분리되고 두 timestamp는 각각 필수 입력으로 적재된다. +- [ ] UTC template export, offset/Z instant 보존, offsetless legacy 한계, 현재 초 정밀도 및 nonzero subsecond 사전 거절이 공통 validator와 일치한다. +- [ ] createdAt ASC/id ASC가 실제 DB와 독립 reopen에서 안정적이며 기존 서로 다른 생성일 순서를 유지한다. +- [ ] 실제 암호화 v1/v2 round-trip 및 재export가 template 전체 field/steps/순서를 비교하고 날짜 몇 개만 별도 mock한 테스트로 대체하지 않는다. +- [ ] template/step write 실패와 취소·검증 실패가 부분 활성 교체를 만들지 않는다. 원본 보존 read-back을 제출한다. +- [ ] A09 정상 staging과 D02 손상 Recovery에서 같은 materializer를 사용하고 template metadata 보존을 독립 재열기로 확인한다. +- [ ] Android↔iOS 두 방향의 실제 파일 이동에 template timestamp 비교 증거가 있다. 기존 A02/A03/T01/T02 release 증거를 재사용할 수 있으나 host-only 결과로 대체하지 않는다. +- [ ] analyze, 관련 DAO/backup/workflow 회귀 및 필요한 전체 suite를 수행하고 정확한 source SHA·명령·결과를 기록한다. generated outputs는 커밋하지 않는다. +- [ ] 이미 손실된 값의 추정 복구와 offsetless legacy의 source-zone 복구를 주장하지 않는다. 저장소 구현/통합/실기기 검증 상태를 따로 보고한다. + +A09/D02 통합 또는 양 플랫폼 실증이 남아 있으면 저장소 수정과 unit 회귀를 완료한 것으로 기록하되 **D07 전체는 OPEN**으로 둔다. 아직 실행하지 않은 항목, 실제 native 암호화 검증, 실기기 파일 전달을 서로 대체하지 않는다. + +## 실제 grill-with-docs 문답 전체 + +아래는 전담 agent와 root 사이의 실제 질문·답변·보충·정정 원문이다. root가 사용자 지시에 따라 답변했으며 사용자에게 별도 질문하지 않았다. 코드로 확인 가능한 사항은 먼저 조사했다. + +### Q1 — 일반 편집과 복원용 timestamp 저장 + +**전담 agent:** + +> D07에서 grill-with-docs를 적용했고 고정 commit a4dfbe426b33815c96386fe3cb6af22186daf98a의 실제 DAO/export/restore를 대조했습니다. Q1: 일반 템플릿 편집의 put(now)는 기존 createdAt을 보존하고 updatedAt만 변경하는 현재 계약을 유지하되, 검증된 Backup Restore용 저장 경로는 createdAt/updatedAt을 각각 필수로 받고 백업 값 그대로 복원하도록 분리할까요? 권고는 기존 DAO를 무조건 두 timestamp upsert로 바꾸지 않고 명시적인 restore 경로를 추가해 활성 편집의 생성일 위조/리셋을 피하는 것입니다. 전체 교체에서 같은 ID의 기존 template가 있어도 backup 두 값이 권위 있고, restore 실행 시각/Backup Cutoff/대상 기존 생성일로 대체하지 않습니다. 기존 잘못된 restore로 이미 createdAt=updatedAt가 된 자료는 근거 없이 추정 복구하지 않으며, updatedAt Q1 채택합니다. 일반create/edit put은기존createdAt보존/updatedAt현재명시저장시각 기존계약유지, 검증된restore전용경로는createdAt/updatedAt각필수로받아그대로적재. 기존DAO모든업서트에2값허용해일반edit생성시각변조하지않도록명확한port/메서드경계. 전체교체시backup2값이권위,대상기존값/restore시간/cutoff대체금지. 이미손실된과거값은추정복구금지, 원본정상backup재복원은별도사용자의명시선택. updatedAt D07 Q2: getAll() 정렬은 createdAt ASC만 있고 같은 timestamp의 tie 계약은 없습니다. 또 locked Drift 2.34.3은 기본 DateTime을 epoch 초 INTEGER로 저장하고 local DateTime으로 읽으며, export는 그대로 toIso8601String() 하므로 현재 template timestamp 문자열에 offset이 빠질 수 있습니다. 권고는 template 메타데이터 두 값만 새 export에서 UTC ISO로 직렬화하고, 명시 offset/Z가 있는 기존 v1/v2 입력은 같은 UTC instant로 복원하며, offset 없는 legacy 입력은 과거 parser 호환을 유지하되 원래 source zone을 추정해 복원했다고 주장하지 않는 것입니다. timestamp 동률은 createdAt ASC 다음 안정적인 id ASC로 정해 복원 전후/재실행 순서가 같게 하고, 서로 다른 createdAt의 기존 순서는 유지합니다. DB 저장 해상도는 현재 초 단위를 유지하며 새 subsecond schema/format 확장은 D07에 넣지 않고, round-trip 동등성은 실제 저장 가능한 source row instant 기준으로 검증할까요? 임의 외부 subsecond 입력의 손실/거절 여부는 D05의 표현 가능성 계약으로 명시하고, 모든 날짜나 Schedule civil 표현의 UTC 전환으로 확대하지 않을 것을 권고합니다. + +**Root의 Q1 보충검토(위 Q2와 교차 수신):** + +> Q1 보충검토: timestamp '그대로'는현재Drift저장정밀도와wire ISO표현정밀도를확인해정의하세요. 현재DAO dateTime이초정밀도이고export도초에서나왔다면동일supportedformat의actualexportroundtrip instant동등성이고,외부조작된subsecond값은D05지원/정규화/거부정책명확화가필요합니다. D07만으로timestampcolumn/backupformat대규모변경을추정하지말고실제source근거와tradeoff를다음질문에포함해주세요. + +**전담 agent의 근거 정정:** + +> D07 Q2 근거 정정: 위 메시지의 locked Drift 버전 ‘2.34.3’은 설치된 다른 cache 버전을 잘못 지칭했습니다. 실제 pubspec.lock은 Drift 2.31.0이며, 그 버전 mapping.dart:72/174에서도 epoch 초 저장 및 local DateTime 재생성을 확인했습니다. timestamp 동작에 대한 결론은 같지만 이슈에는 잘못 지칭한 원문과 이 정정을 함께 보존하고 근거는 정확한 2.31.0으로 표기하겠습니다. A08 WIP의 DB fixtures/tests는 고정 commit 근거로 사용하지 않고 기존 table/DAO/lock/source만 참조합니다. + +**Root:** + +> Q2 채택합니다. template createdAt/updatedAt에한해 새export UTC ISO(Z)직렬화, offset/Z입력은sameinstant복원, offset없는legacy는기존parser호환유지하되sourcezone원천미상으로cross-zone원래instant복원을보장하지않습니다. 일정civil표현/전체날짜UTC전환금지. getAll createdAt ASC,id ASC로동률안정화하고서로다른createdAt순서유지, 실제같은타임스탬프역순삽입/복원/재오픈회귀. DBepoch초해상도유지, 실제export된source row instant기준roundtrip. 임의nonzero subsecond 입력은조용히잘라서'그대로'라고하지말고D05공유validator의표현가능성검사로파괴적apply전명시거부하는방향을권고확정합니다(기존정상앱export가실제로subsecond를생성한증거있으면호환재검토질문). .000허용/동일instant의offset정규화는문제없음; 음수epoch/range/zone없는legacy도negative와한계명시. 정밀도변경migration/format증가는없음. D05구현전에필요한최소templatevalidator는D07이담당하되별도중복validator생성금지, 정상/staging/손상restore동일parse규칙. offsetless현재기기해석으로old자료가바뀔수있는한계를이슈명확히남기고과거원문/zone추정복구금지. + +**Root의 버전 정정 확인:** + +> 정정확인했습니다. 실제lock2.31.0/source매핑을근거로하고잘못된2.34.3원문과정정모두문답에보존하세요. 현재A08 WIP fixture는기준증거로사용하지않고구현착수시finalhandoff로확인합니다. + +### Q3 — 실제 round-trip과 통합 완료 경계 + +**전담 agent:** + +> D07 Q3: 완료 검증은 mock DAO에 두 인자가 전달됐다는 검사 대신, 서로 생성/수정 순서가 다른 A(created=1일, updated=4일), B(created=2일, updated=3일), 동률 ID 역순 fixture를 실제 DB→암호화 export→검증/preview→전체 교체→독립 reopen→재export로 통과시키고 template IDs/이름/두 instant/step IDs·이름·분·순서·nextId 및 getAll 순서를 비교하는 것으로 확정할까요? 권고는 v1/v2 양쪽, 대상 같은 ID의 다른 timestamp, 편집 후 createdAt 불변/updatedAt 갱신, clock 역행, UTC/offset/legacy offsetless/비정수초 거절, second-template/step write 실패 시 전체 원본 보존까지 포함합니다. A09 정상 staging과 D02 손상 Recovery는 같은 materializer로 실제 template read-back을 통합하며, 두 경로가 미구현이면 D07의 DAO/unit만 완료했다고 이슈 전체를 닫지 않습니다. Android↔iOS의 실제 파일 이동과 native 암호화 DB 증거는 T01/T02/A02·A03 release 통합에 연결해 host SQLite round-trip과 분리하고, 구현 완료에는 두 모바일 방향의 template timestamp 비교 증거까지 요구하되 다른 이슈의 OS picker 전체 매트릭스를 복제하지 않는 것을 권고합니다. 새 용어/고비용 tradeoff는 없어 CONTEXT/ADR 신규 작성은 하지 않겠습니다. + +**Root:** + +> Q3 채택합니다. 실제DB→암호화export→검증preview→전체교체→독립reopen→재export로2시각/IDs/이름/steps의ID·순서·분·nextId/getAll동률순서비교. A/B생성순서와수정순서반대,타깃동일ID다른시간,기존editcreatedAt보존+updatedAt변경,시계역행/UTCoffset/legacyoffsetless/비정수초거절/두번째template나step쓰기실패원본전체보존 포함. v1/v2정상app생성자료와지원표현을각각확인; fixture에임의원래zone를채워서legacy모호성숨기지마세요. A09staging정상/D02손상복원같은materializer/readback필수,그통합미완료면repo수정상태와전체OPEN구별. Android↔iOS실제파일두방향timestamp비교는관련기존release검증증거재사용/추가관측으로충족가능, picker전체matrix중복금지. hostSQLite/nativeSQLCipher/실기기각증거수준분리. 새CONTEXT/ADR불필요;기존timestamp/backup문서의정확표현만구현시정비. 실제Q1Q2Q3+버전정정+root보충모두원문포함하고동결해주세요. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 16ec83e0..828f1ba4 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -1027,16 +1027,18 @@ "`backup_service.dart:187-193,397-405`; `preparation_template_dao.dart:40-45`", "createdAt/updatedAt을 별개로 저장하고 template 포함 round-trip 동등성 검사." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_d07", + "issue_number": 624, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/624", "decisions_file": "plans/audit-2026-09-23/issues/D07.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 37 + "sequence": 37, + "readback_verified": true, + "issue_body_sha256": "7665fd5ae43558d9a0bd8730d554babfec7528eee9e64f39455f6b0d87c53d2c" }, { "audit_id": "U04", From 3ba88146ee4b4a5807b949e8ea5104ff3ca86448 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 12:50:56 +0900 Subject: [PATCH 57/59] docs: define localized result duration correction --- handoff/audit-2026-09-23-progress.md | 2 + plans/audit-2026-09-23/README.md | 3 +- plans/audit-2026-09-23/issues/U04.md | 157 +++++++++++++++++++++++++++ plans/audit-2026-09-23/state.json | 12 +- 4 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 plans/audit-2026-09-23/issues/U04.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index e6e8bf59..5713a257 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -144,3 +144,5 @@ D06#623 전담실제4문답·27개검증·10개AC 게시/readback완료,37/64( 누락되어있던기존14개 issue_body_sha256을GitHub read-only재조회해현재local본문과모두exact일치확인후state에기록했다. 원격본문수정없음; legacy-issue-readback-20260924.json 보존. A08 추가집중45/UI+연관통합26 통과보고및로그확인. root는 /tmp/a08-ui KOEN200% rollback/deliverypending4PNG직접확인(새receipt본문/버튼접근가능,ENunderlay단계라벨/필드말줄임은U08전체레이아웃범위). 최종analyze/full/sourcefreeze는아직미완료,마지막원격검증제품C01과혼동금지. D07#624 실제3문답+Drift버전정정·18검증·10AC 게시/readback완료,38/64(미생성26). template restore전용createdAt/updatedAt보존·template-onlyUTCexport·id동률정렬·legacyoffsetless한계·nonzero subsecond사전거부/공유validator·A09/D02/양플랫폼통합검증확정. U04 새전담 /root/grill_u04 착수. A08최종analyzer정리중이고sourcefreeze대기. + +U04#625 실제3문답/보충·21검증·8AC 게시/readback완료,39/64(미생성25). 결과기간만기존locale-awareformatter재사용,abs/초버림/기존suffix·outcome·점수유지,realheadline음성회귀및KOEN/200%표본;U05새전담 /root/grill_u05 착수. A08첫freeze47개(e7e9cfb8...)의첫full=1008pass/7fail로구freeze를보존후보완중. DAOmetadataequality5개와startwidgetlatch2개를분리확인; legacyDAO가oldrowconcurrencymetadata덮지않도록제외하고AFTERtrigger후sameTXreadback,watch단일SQLsnapshot으로변경해집중63통과. root가watch기존scheduleTimeASC유실도찾아정렬복구/회귀요청, 새freeze/전체재검증대기. 첫full실패를최종성공으로표시금지. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index b06fbfb0..29676fd0 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -54,8 +54,9 @@ | O02 | [#622](https://github.com/DevKor-github/OnTime-front/issues/622) | 최종 바이너리와 실제 배포 범위에 맞춘 개인정보 정책·스토어 신고 및 공개 검증 | | D06 | [#623](https://github.com/DevKor-github/OnTime-front/issues/623) | 실제 4문답·최초 미백업 age/동시 export/7일 숨김·27개 검증 기준 확정 | | D07 | [#624](https://github.com/DevKor-github/OnTime-front/issues/624) | 실제 3문답·템플릿 두 시각/UTC·legacy/안정 정렬·18개 검증 기준 확정 | +| U04 | [#625](https://github.com/DevKor-github/OnTime-front/issues/625) | 실제 3문답·1시간/2시간 표시·기존 현지화 formatter 재사용·21개 검증 기준 확정 | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **38/64개**를 게시하고 원격 본문 동일성을 확인했으며 **26개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했다. C01은4529d9f8로 구현해 최종 원격959 tests·88.72%·APK를 통과했고 A08 구현 중이다. A08의 현재 부분 검증은 집중52개와 역사 migration5개이며 최종 전체 검증은 남아 있다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **39/64개**를 게시하고 원격 본문 동일성을 확인했으며 **25개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했다. C01은4529d9f8로 구현해 최종 원격959 tests·88.72%·APK를 통과했고 A08 구현 중이다. A08의 현재 부분 검증은 집중52개와 역사 migration5개이며 최종 전체 검증은 남아 있다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/issues/U04.md b/plans/audit-2026-09-23/issues/U04.md new file mode 100644 index 00000000..3d9d3be5 --- /dev/null +++ b/plans/audit-2026-09-23/issues/U04.md @@ -0,0 +1,157 @@ +# [P2][U04] 결과 화면에서 1시간·2시간이 0분으로 표시되는 오류를 고친다 + +상태: 전담 3문답과 보충 질의 완료, 본문 동결. 구현·Flutter 테스트·기기 실행은 아직 수행하지 않았으며 아래 표는 검증 계획이다. +전담: `/root/grill_u04`; 적용 skill: `/Users/ejunpark/.agents/skills/grill-with-docs/SKILL.md`. +조사 고정 commit: `63913cd715a5bf35c5495619ad8fd600e526f43e`. 동시에 진행 중인 A08 작업 파일은 이 이슈의 제품 근거에 포함하지 않았다. + +## 문제와 사용자 영향 + +준비 완료 결과의 기간을 만드는 `formatEalyLateTime`은 절댓값에서 시간과 남은 분을 계산한 뒤 **시간과 분이 모두 양수일 때만** 시간을 출력한다. 따라서 정확히 ±3,600초와 ±7,200초가 모두 `0분`으로 표시된다. 같은 조건 때문에 ±3,601~3,659초도 `0분`이다. 실제 준비 완료의 나가기 전 남은 초가 결과 화면으로 전달되므로, 일정이나 점수 저장이 정상이어도 사용자는 1시간의 차이가 없어진 잘못된 결과를 읽는다. + +P2로 처리한다. 자료 손실이나 준비 완료 저장 실패와는 구별되는 표시 오류지만 정상 흐름에서 확정적으로 발생하며, 결과를 신뢰하지 못하게 한다. 별도 새로운 기간 정책이나 타이머 엔진 없이 기존 현지화 formatter에 연결하여 해결할 수 있다. + +예시: + +- +3,600초, `isLate=false`: 현재 `0분 일찍 준비했어요` → `1시간 일찍 준비했어요`. +- -7,200초, `isLate=true`: 현재 `0분 지각했어요` → `2시간 지각했어요`. +- 영어의 +3,600초: 현재 한국어 단위가 섞인 `0분 early` → `1 hour early`. +- +3,599초는 `59분`이고 +3,601초는 `1시간`이다. 분 미만 초를 버리는 기존 표시 정책을 유지한다. + +## 고정 소스 근거 + +| 근거 | 확인한 사실 | +|---|---| +| [문제 formatter](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/presentation/shared/utils/time_format.dart#L34-L45) | `abs()`, 시간 몫과 남은 분을 계산하고 `hours > 0 && minutes > 0`일 때만 시간을 넣는다. 이는 실행 결과가 아니라 소스에서 직접 확인한 결함이다. | +| [실제 결과 headline](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/presentation/early_late/screens/early_late_screen.dart#L95-L134) | 오타 helper의 제품 호출부는 이 한 곳이다. 기간 뒤에 별도 `isLate`에 따른 기존 ARB suffix를 붙인다. | +| [기존 공통 기간 formatter](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/presentation/shared/utils/duration_format.dart#L4-L21) | 시간과 분을 각각 양수일 때 추가하고 모두 없으면 0분을 쓴다. `Duration.inHours/inMinutes`를 쓰므로 초는 버리고 24시간을 하루로 감지 않아 25시간도 표현한다. 양수 절댓값 입력이 이 이슈의 계약을 만족한다. | +| [KO 단위](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/l10n/app_ko.arb#L102-L103), [EN 단복수](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/l10n/app_en.arb#L343-L360) | 이미 `hourFormatted`/`minuteFormatted`가 있으며 EN의 1 hour/2 hours와 1 minute/0 minutes를 지원한다. 새 번역 key가 필수인 문제가 아니다. | +| [준비 완료 값 전달](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/presentation/alarm/screens/alarm_screen.dart#L88-L100), [완료 후 navigation](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/presentation/alarm/screens/alarm_screen.dart#L139-L157) | 나가기 전 남은 초와 별도 `isLate`를 보관하고 완료 상태 전환 후 결과 화면으로 보낸다. U04는 이 계산·저장·navigation 시점을 바꾸지 않는다. | +| [결과 Bloc](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/presentation/early_late/bloc/early_late_screen_bloc.dart#L18-L39) | 응원 문구 분류는 seconds의 부호와 `ceil(absSeconds/60)`를 사용한다. headline의 분 버림과 다른 목적이므로 U04에서 일괄 통일하지 않는다. | +| [현재 formatter 회귀](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/test/presentation/shared/utils/time_format_test.dart#L20-L23) | -90초와 3,900초만 검사하여 시간은 양수이고 남은 분은 0인 결함을 놓친다. `formatTime(3600)` 테스트는 다른 함수여서 이 화면 오류를 막지 못한다. | +| [기존 결과 Bloc 테스트](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/test/presentation/early_late/bloc/early_late_screen_bloc_test.dart) | 결과 상태와 문구/이미지 존재는 검사하지만 실제 headline의 정확한 시간 문자열을 검사하지 않는다. | +| [결과 레이아웃](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/presentation/early_late/screens/early_late_screen.dart#L28-L54), [이미지·문구 영역](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/lib/presentation/early_late/components/early_late_message_image_widget.dart#L18-L44) | Stack/Column, 고정 간격과 화면 비율 이미지가 있다. 실제 작은 화면·큰 글자 실패 여부는 U08의 실행 과제이며 U04 조사만으로 실패했다고 판정하지 않는다. | + +## 기존 언어와 결정 + +[CONTEXT](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/CONTEXT.md)의 **Schedule Outcome**, **Preparation Run**, **Preparation Action Event**, **Local Punctuality Score**를 유지한다. 이 결과 headline은 기간의 사용자 표시다. 표시 문자열을 Outcome 자체, 점수 자료, 타이머의 원본 상태로 쓰지 않는다. + +[ADR 0029](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/docs/adr/0029-derive-preparation-runs-from-action-events.md)의 run/action 기반 시간 계산과 [ADR 0025](https://github.com/DevKor-github/OnTime-front/blob/63913cd715a5bf35c5495619ad8fd600e526f43e/docs/adr/0025-calculate-punctuality-score-locally.md)의 점수 정책을 변경하지 않는다. 기간 출력 조건에 관한 좁고 되돌리기 쉬운 수정이므로 새 용어·CONTEXT 항목·ADR을 만들지 않는다. + +## 확정 구현 계약 + +1. 결과 headline의 기간은 **초의 절댓값**을 사용한다. 부호를 텍스트에 다시 출력하지 않는다. 기존 `isLate` suffix와 색상은 그대로 유지한다. +2. 표시 정밀도는 분이다. 분 미만 초를 버려 0~59초는 `0분`/`0 minutes`로 표시한다. 반올림, 올림, `1분 미만`, 정확히 0초 전용 문구를 추가하지 않는다. +3. 시간은 1 이상이면 반드시 표시한다. 남은 분이 0이면 시간만, 남은 분이 있으면 시간과 분을 표시한다. 24시간을 0시간이나 날짜로 감지 않는다. +4. 실제 유일한 callsite를 `formatDuration(context, Duration(seconds: earlyLateTime.abs()))`로 연결한다. 기존 helper의 양수 Duration 계약을 활용한다. 다른 소비자의 음수 입력 의미까지 바꾸는 전역 formatter 수정은 하지 않는다. +5. 사용하지 않게 된 오타 helper `formatEalyLateTime`과 해당 낡은 테스트를 제거·대체한다. 모든 참조를 검색하여 stale import나 Widgetbook/테스트 참조가 남지 않게 한다. 새 formatter 계층, 동명의 복사 구현, 전체 파일 rename은 만들지 않는다. +6. 기존 KO/EN 단위와 단복수 표현을 재사용한다. 결과 기간만 U05보다 먼저 현지화한다. random 응원 문구나 전체 결과 번역, 12/24시간, 다른 화면 문구를 함께 고치지 않는다. +7. 완료 시점 계산, route의 초값과 `isLate`, Outcome/점수 저장, Bloc의 응원 문구용 ceil, timer 진행·재개·복원은 바꾸지 않는다. 정상 완료 경로에서 실제 부호 불일치를 재현한 증거가 발견되면 T04/C04에 연결하고, 단순 잠재성만으로 별도 버그를 단정하지 않는다. +8. 새로 길어진 결과 기간으로 직접 발생한 headline/버튼의 접근성 실패가 확인되면 필요한 최소 레이아웃 수정과 회귀만 포함한다. 기존 전체 결과 배치 문제는 U08에 근거와 함께 남긴다. 기존 실패를 성공으로 숨기거나 임의로 글자 배율을 낮춰 해결했다고 하지 않는다. + +## 범위·연결·구현 순서 + +| 연결 | 책임 경계 | +|---|---| +| U05 — 전체 KO/EN 문구 | 결과 기간 단위만 선반영한다. random 응원 문구의 한국어, 설정·백업·오류 등 전체 번역 및 시간 표시 정책은 U05 책임이다. U04 성공은 U05 완료가 아니다. | +| U08 [#597](https://github.com/DevKor-github/OnTime-front/issues/597) | 작은 화면/200% headline·버튼의 직접 영향 표본을 제공한다. 모든 화면·긴 이름·모든 이미지/메시지 조합을 검증했다고 확대하지 않는다. | +| C04 — 준비 시간 순수 계산기 | U04는 이미 결정된 초값을 표시한다. 계산기를 새로 설계하거나 화면 테스트에 복제하지 않는다. | +| A10 [#608](https://github.com/DevKor-github/OnTime-front/issues/608) | absolute instant와 civil time, 시간대/DST는 A10 책임이다. 기간 formatter가 DateTime/time zone을 새로 참조하지 않는다. | +| T04 [#614](https://github.com/DevKor-github/OnTime-front/issues/614) | 실제 준비 완료 workflow 회귀를 재사용한다. route와 저장의 실제 불일치가 발견되면 별도로 재현 증거를 연결한다. | +| T01 [#615](https://github.com/DevKor-github/OnTime-front/issues/615) | 앱 전체 Android/iOS E2E와 오프라인 출시 증거는 별도다. U04의 국소 UI 자동 검증 완료를 전체 실기기 검증 완료로 표현하지 않는다. | + +의존 작업이 완료되어야만 이 작은 표시 오류를 수정할 수 있는 구조는 아니다. 전체 감사의 높은 우선순위 작업 뒤 순서대로 처리하되 U05/C04의 대규모 작업을 선행 조건으로 만들지 않는다. DB schema, backup format, 네트워크, 권한, 서비스, 새 package를 추가하지 않는다. + +## 수치 및 실패 검증 행렬 + +아래 수치 표의 ±는 양수·음수 각각 별도 입력을 뜻한다. 기대값은 구현에서 동적으로 재계산해 만든 값이 아니라 아래의 고정 사용자 계약이다. 결과 화면에는 여기에 기존 early/late suffix가 붙는다. + +| ID | 입력 초 | KO 기간 | EN 기간 | 잡아야 할 실패 | +|---|---:|---|---|---| +| N01 | 0 | 0분 | 0 minutes | 빈 문자열·음수 0·불필요한 시간 | +| N02 | ±1, ±59 | 0분 | 0 minutes | 초 반올림/올림 정책이 몰래 변경됨 | +| N03 | ±60 | 1분 | 1 minute | abs 누락·단수 실패 | +| N04 | ±3,599 | 59분 | 59 minutes | 경계 직전 시간 올림 | +| N05 | ±3,600 | 1시간 | 1 hour | 원래의 0분 결함 | +| N06 | ±3,601, ±3,659 | 1시간 | 1 hour | 정확한 배수만 고쳐 여전히 0분 | +| N07 | ±3,660 | 1시간 1분 | 1 hour 1 minute | 남은 분 누락·복수형 오류 | +| N08 | ±7,199 | 1시간 59분 | 1 hour 59 minutes | 시간·분 조합 경계 | +| N09 | ±7,200, ±7,201 | 2시간 | 2 hours | 2시간도 0분·단복수 오류 | +| N10 | ±86,399 | 23시간 59분 | 23 hours 59 minutes | 24시간 근처 포맷 손실 | +| N11 | ±86,400 | 24시간 | 24 hours | DateTime/day wrap로 0시간 | +| N12 | ±90,061 | 25시간 1분 | 25 hours 1 minute | 24시간 모듈러·초 포함/올림 | + +| ID | 실제 경로/조건 | 기대 및 증거 | +|---|---|---| +| V01 | 실제 EarlyLateScreen에 +3,600/+7,200초와 정상 early 조합 | headline 전체 기간+suffix를 TextSpan까지 읽어 검증한다. 수정 전 화면이 0분이어서 실패하는 증거를 남긴다. | +| V02 | 실제 EarlyLateScreen에 -3,600/-7,200초와 정상 late 조합 | 기간은 양수, 기존 late 문구/색상을 유지한다. helper만 성공하고 화면이 옛 helper를 쓰면 실패해야 한다. | +| V03 | 화면 +3,601초 및 0초 | 1시간/0분 정책을 직접 확인한다. 0초에서 새 Outcome·문구 정책을 만들지 않는다. | +| V04 | 실 AppLocalizations KO→EN locale 변경 | 같은 수치의 headline이 1시간→1 hour로 바뀐다. 캐시된 한국어 기간이나 새 부호·수치 변화가 없다. | +| V05 | EN 1시간1분/2시간/0분 | 기존 ARB 단복수와 공백·suffix 결합이 맞는다. 응원 문구 전체 현지화 성공으로 확장하지 않는다. | +| V06 | 일반 크기 및 작은 viewport/200% 텍스트 | viewport 크기·배율을 기록한다. headline을 읽을 수 있고 나가기 버튼 접근이 가능하다. 직접 신규 overflow와 기존 U08 실패를 분리하며 미실행 조건을 숨기지 않는다. | +| V07 | 기존 준비 완료 navigation/flow 회귀 | 완료 후 전달한 초와 suffix가 유지된다. 테스트 안에 DB/점수/timer 로직을 복제하지 않는다. | +| V08 | 기존 formatDuration 소비자 회귀 | 기존 상세/시작/입력 화면 관련 테스트를 재사용한다. helper 구현이 그대로면 모든 formatter의 전면 중복 테스트를 만들지 않는다. | +| V09 | 참조 및 정적 검사 | 제거한 helper 참조가 없고 analyze가 통과한다. 코드 생성이 필요한 실제 변경이 있으면 ignored 산출물만 재생성하고 commit에 넣지 않는다. | + +## 검증 실행과 완료 증거 + +- 먼저 실제 결과 widget의 ±3,600/±7,200 기대를 추가하고 기존 소스에서 실패를 확인한다. 공통 `formatDuration`이 이미 맞는다는 사실만으로 negative 증거를 대체하지 않는다. +- 수정 후 N01–N12의 실제 localization 기반 테스트와 V01–V09 관련 검증을 수행한다. RichText의 부분 단어 존재만 보는 약한 assert 대신 headline의 완성된 텍스트·의미를 확인한다. +- random 응원 문구는 테스트의 exact 기대에 넣지 않는다. 해당 문구의 선택기를 새로 만들거나 product 계산을 복제하지 않고 기간 headline을 정확히 검증한다. +- 화면 증거는 KO/EN, 초값, `isLate`, viewport, 글자 배율, source SHA를 함께 남긴다. widget 렌더와 실제 Android/iOS 실행을 구별해 기록한다. +- 실제 변경 범위에 맞춰 관련 테스트와 analyzer를 실행하고, 감사 통합 branch의 전체 테스트/CI에서 회귀 여부를 확인한다. 이미 검증한 고정 source 결과는 exact SHA가 같을 때 재사용하며 숫자를 추정하지 않는다. +- 로컬 수정, commit, PR CI, merge, 기기 적용 상태를 구분한다. 본문 작성만으로 구현·테스트 통과·배포 완료를 표시하지 않는다. + +## 수용 기준 + +- [ ] 실제 결과 headline의 ±3,600/±7,200초가 각각 1시간/2시간이며 0분이 아니다. +- [ ] 3,601~3,659초, 직전·직후 분 경계, 음수와 24시간 이상을 N01–N12 계약대로 표시한다. +- [ ] 결과 기간은 기존 locale-aware formatter와 ARB를 사용하며 KO/EN 단복수·locale 전환이 정상이다. +- [ ] 제거된 오타 helper 참조가 남지 않고 별도 중복 formatter를 만들지 않았다. +- [ ] 실제 EarlyLateScreen의 수정 전 실패와 수정 후 성공이 있으며 helper 단위 성공만으로 끝내지 않았다. +- [ ] 초 버림, abs, 기존 suffix/색상/응원 문구 선택, 완료 계산과 저장·점수 정책을 유지한다. +- [ ] 작은 화면/200%의 직접 영향 표본·접근성 결과를 기록하고, 기존 U08 문제와 미실행 조건을 정확히 남겼다. +- [ ] 관련 회귀/analyze/통합 CI의 실제 source SHA 및 결과를 기록했다. 앱 전체 mobile E2E 완료 여부와 분리했다. + +## 실제 grill-with-docs 전체 문답 및 보충 원문 + +아래는 전담 subagent가 root에게 한 질문과 사용자 대행 root의 실제 답변이다. 문답의 간결한 공백 표현도 원문 그대로 보존한다. 별도 사용자에게 질문하지 않았다. + +### Q1 — 표시 정밀도와 수정 범위 + +**전담 질문 원문** + +> U04 Q1 — 실제 formatter는 abs(seconds)를 분 단위로 버리면서 hours>0 && minutes>0일 때만 시간 수를 넣어 ±3600/±7200뿐 아니라 ±3601~3659도 0분이 됩니다. 결과 화면의 부호(늦음/일찍), Outcome 저장, Bloc의 메시지 선택용 ceil은 별개입니다. 권고: U04는 기존 '절댓값, 초 버림, 0~59초=0분' 표시 정책을 유지하고, hours>0이면 분이 0이어도 시간을 표시(3600/3601→1시간,3660→1시간1분,7200→2시간)하며 부호/Outcome/메시지 범주 계산은 변경하지 않습니다. 59초→1분 올림이나 정확히0초 전용 문구는 별도 제품 결정으로 남깁니다. 이 좁은 계약으로 확정할까요? + +**root 답변 원문** + +> Q1 채택합니다. U04는절댓값+초버림+0~59초=0분 기존표시계약유지, hours>0이면minutes==0이어도시간표시. ±3600/3601~3659=1시간,±3660=1시간1분,±7200=2시간. 부호/늦음·일찍/outcome저장/메시지선택ceil/점수정책변경없음. 59초올림/0초전용문구변경은이이슈범위밖이며새기능요청으로추정하지않습니다. 정확경계와직전직후/음수/긴기간을KOEN formatter+실제결과UI에서증명하되U05전체번역/U08전체레이아웃완료로확대하지마세요. formatter가여러중복경로면실제공통사용점을조사하고이문제영향경로에한정해수정계약을정리해주세요. + +### Q2 — 기존 현지화 formatter 재사용 + +**전담 질문 원문** + +> U04 Q2 — 실제 formatEalyLateTime 호출은 결과 화면 1곳뿐이고, calendar/start 화면은 이미 AppLocalizations.hourFormatted/minuteFormatted를 쓰는 formatDuration(context, Duration)을 사용합니다. 따라서 숫자 수정만 하면 EN에서도 '1시간 early'가 남습니다. 권고: 이 결과의 절댓값 기간만 기존 locale-aware formatDuration(context, Duration(seconds: value.abs())) 경로를 재사용하도록 연결하고, 더는 쓰지 않는 오타 helper formatEalyLateTime은 호출부/테스트를 함께 제거·대체합니다(새 formatter 계층/전체 rename 없음). KO 1시간/EN 1 hour, 2 hours, 1 hour 1 minute, 0 minutes를 검증하되 random 응원 문구·그 외 한국어 혼입/12·24h는 U05에 남깁니다. KOEN 이 결과 표시 연결을 U04에 포함하고 U05의 해당 부분만 선반영으로 기록할까요? + +**전담 조사 보충 원문** + +> U04 조사 보충: 준비 완료 화면은 _pendingEarlyLateSeconds(나가기 전 남은 초)와 별도 isLate를 route로 전달하고, 결과 Bloc은 seconds<0으로 응원 문구 분류합니다. 둘의 잠재적 불일치나 ScheduleOutcome 의미를 이 formatter 이슈에서 재정의하지 않겠습니다. 사용자에게 보이는 headline만 '절댓값 기간 + 기존 isLate suffix'로 그대로 유지하고, 테스트는 정상 완료 경로의 부호 조합을 사용하겠습니다. 근거 고정 SHA는 처음 확인한 63913cd715a5bf35c5495619ad8fd600e526f43e이며 A08 제품 WIP는 근거에 섞지 않습니다. + +**root Q2 답변 원문** + +> Q2 채택합니다. 유일한결과기간callsite를기존locale-aware formatDuration(context,Duration(seconds:value.abs()))에연결하고실제공통helper가Q1의초버림/0분/1시간경계를충족하는지소스로확인하세요. 미사용오타helper는callsite/test포함제거·대체, 새formatter계층/전체rename불필요. KO/EN이결과기간만현지화(1 hour/2 hours/1 hour 1 minute/0 minutes)하며 늦음/일찍기존문맥·random응원문구·전체번역/12·24h/시간대정책은변경하지않습니다. U05해당기간단위부분선행반영으로연결하되전체U05완료아님. 동일helper기존소비자에행동변경없으면기존관련tests재사용하고모든formatter전면테스트중복불필요. + +**root 보충 답변 원문** + +> 보충동의합니다. seconds와isLate의기존별도책임은이formatter변경에서재정의하지말고절댓값기간+기존suffix유지. 다만실제정상완료경로에서불일치를재현할수있는구체증거가발견되면감사의기존T04/C04에연결하여추적하고단순잠재성만으로새범위/버그를단정하지마세요. 고정63913cd7과A08WIP분리표기확인. + +### Q3 — 실제 화면 연결의 회귀와 완료 증거 + +**전담 질문 원문** + +> U04 Q3 — 검증은 기존 helper가 이미 올바르더라도 결과 화면을 연결하지 않으면 기존버그가 남는 것을 반드시 잡아야 합니다. 권고: (1) 실 AppLocalizations를 쓰는 KOEN 기간표시 경계 표(0, ±1/59/60/3599/3600/3601/3659/3660/7199/7200/7201/86399/86400/90061; 25시간1분까지 day-wrap 없음), (2) 실제 EarlyLateScreen headline의 ±3600/±7200/3601/0 및 KOEN 전환 widget 검증으로 수정 전 화면 FAIL→수정 후 PASS, (3) 일반 크기+작은 화면/200%에서 headline/나가기 버튼 접근성 표본을 확인하고 새 길이 때문에 직접 생긴 overflow만 최소 수정, 기존 광범위 결과 화면 배치문제는 U08 연결. 시간대를 바꾸거나 DB/점수/새 timer 엔진을 테스트 fixture로 재구현하지 않고, 기존 완료 flow 회귀를 재사용합니다. 이 정도를 U04 완료 기준으로 하고 실제 mobile 전체 E2E 요구는 T01/U08과 구분할까요? + +**root 답변 원문** + +> Q3 채택합니다. 기존helper단위성공만으로결과연결버그를놓치지않게실제EarlyLateScreenheadline negative→positive필수. 제시KOEN/±경계/25h1m daywrap없음 및실제locale전환, isLate정상조합기존suffix확인. small/200%에서headline·나가기버튼접근표본, 이변경으로직접발생하는overflow최소수정하고기존광범위U08별도. DB/점수/timer재구현없고기존완료flow회귀재사용. 이국소표시변경의자동검증·UI근거완료와앱전체mobileE2E(T01/U08)를분리하며전역실기기완료를U04만의과도한독립blocker로만들지마세요. 미실행구체조건은그대로보고. 전체3문답+보충원문/정확범위/negative matrix/AC로동결해주세요. + +이 문답에서 철회된 별도 설계안이나 추가 승인은 없다. 현재는 이슈 설계 완료 상태이며 구현·검증 결과는 수행 후 별도로 보충한다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 828f1ba4..079b7bb3 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -1050,16 +1050,18 @@ "정확히 1시간·2시간의 결과가 0분으로 표시되는 오류를 수정한다.", "`shared/utils/time_format.dart:34-44`: hours와 minutes가 모두 양수일 때만 시간 출력. ±3600, ±7200, 0/59/60/3599/3601 경계 검증." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_u04", + "issue_number": 625, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/625", "decisions_file": "plans/audit-2026-09-23/issues/U04.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 38 + "sequence": 38, + "readback_verified": true, + "issue_body_sha256": "dbb83e0747220adfac42234e4cd7c0f1e6b696456324f43888469e5c09114d41" }, { "audit_id": "U05", From 126161dab042365c89983934b1673d860c27d8d3 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 13:03:31 +0900 Subject: [PATCH 58/59] fix: save schedule aggregates atomically --- ...-receipts-to-local-aggregate-identities.md | 7 + lib/core/backup/recurring_backup_data.dart | 22 +- lib/core/database/database.dart | 56 +- .../database/schedule_aggregate_schema.dart | 119 +++ lib/data/daos/schedule_dao.dart | 85 +- .../repositories/alarm_repository_impl.dart | 48 +- .../preparation_repository_impl.dart | 63 +- .../recurring_schedule_repository_impl.dart | 5 +- .../schedule_aggregate_repository_impl.dart | 632 ++++++++++++ .../tables/recurring_schedule_tables.dart | 8 + lib/data/tables/schedules_table.dart | 7 + lib/data/tables/user_table.dart | 1 + lib/domain/entities/schedule_save.dart | 78 ++ .../schedule_aggregate_repository.dart | 15 + ...ate_schedule_form_submission_use_case.dart | 35 +- .../load_schedule_form_draft_use_case.dart | 34 +- .../recurring_schedules_use_case.dart | 41 - .../use-cases/schedule_form_submission.dart | 7 + ...le_mutation_alarm_effects_coordinator.dart | 10 + .../use-cases/schedule_save_workflow.dart | 74 ++ ...ate_schedule_form_submission_use_case.dart | 69 +- lib/l10n/app_en.arb | 11 +- lib/l10n/app_ko.arb | 11 +- lib/l10n/app_localizations.dart | 54 + lib/l10n/app_localizations_en.dart | 33 + lib/l10n/app_localizations_ko.dart | 33 + .../bloc/schedule_form_bloc.dart | 155 ++- .../bloc/schedule_form_event.dart | 9 + .../bloc/schedule_form_state.dart | 36 +- .../components/schedule_multi_page_form.dart | 121 ++- pubspec.lock | 2 +- pubspec.yaml | 1 + test/core/backup/backup_service_test.dart | 33 + test/core/database/database_index_test.dart | 2 +- .../database/recurring_migration_test.dart | 63 +- .../schedule_aggregate_migration_test.dart | 180 ++++ test/data/daos/schedule_dao_test.dart | 65 +- .../schedule_local_data_source_test.dart | 7 +- .../recurring_schedule_repository_test.dart | 8 +- .../schedule_aggregate_repository_test.dart | 970 ++++++++++++++++++ .../schedule_aggregate_snapshot_test.dart | 603 +++++++++++ ...chedule_form_submission_use_case_test.dart | 107 +- ...chedule_form_submission_use_case_test.dart | 194 +--- test/fixtures/database/README.md | 12 + test/fixtures/database/schema_v1.sql | 15 + test/fixtures/database/schema_v2.sql | 19 + .../recurring_form_integration_test.dart | 345 ++++++- .../bloc/schedule_form_bloc_test.dart | 80 +- .../schedule_multi_page_form_test.dart | 23 +- 49 files changed, 4043 insertions(+), 565 deletions(-) create mode 100644 docs/adr/0037-bind-schedule-save-receipts-to-local-aggregate-identities.md create mode 100644 lib/core/database/schedule_aggregate_schema.dart create mode 100644 lib/data/repositories/schedule_aggregate_repository_impl.dart create mode 100644 lib/domain/entities/schedule_save.dart create mode 100644 lib/domain/repositories/schedule_aggregate_repository.dart create mode 100644 lib/domain/use-cases/schedule_save_workflow.dart create mode 100644 test/core/database/schedule_aggregate_migration_test.dart create mode 100644 test/data/repositories/schedule_aggregate_repository_test.dart create mode 100644 test/data/repositories/schedule_aggregate_snapshot_test.dart create mode 100644 test/fixtures/database/README.md create mode 100644 test/fixtures/database/schema_v1.sql create mode 100644 test/fixtures/database/schema_v2.sql diff --git a/docs/adr/0037-bind-schedule-save-receipts-to-local-aggregate-identities.md b/docs/adr/0037-bind-schedule-save-receipts-to-local-aggregate-identities.md new file mode 100644 index 00000000..261e930d --- /dev/null +++ b/docs/adr/0037-bind-schedule-save-receipts-to-local-aggregate-identities.md @@ -0,0 +1,7 @@ +--- +status: accepted +--- + +# Bind schedule saves to local aggregate identities + +Schedule saving uses one atomic schedule/place/preparation/revision transaction and installation-local store/aggregate identities, versions, and the last mutation receipt in schema 3, rather than a global mutation ledger. New schedule drafts additionally fence an absent target with the reviewed durable revision; this deliberately requires explicit re-review after unrelated edits so an old create cannot recreate a deleted schedule without retaining tombstones, while existing-schedule edits compare only their dependent aggregate versions. Portable backup versions 1 and 2 exclude these identities and receipts: restore/reset must issue a fresh store identity and fresh aggregate identities even if user IDs and revisions match; recurring split receipts remain on the stable series root, and mere occurrence materialization does not advance its edit version. diff --git a/lib/core/backup/recurring_backup_data.dart b/lib/core/backup/recurring_backup_data.dart index 66d1b8b1..3852f86e 100644 --- a/lib/core/backup/recurring_backup_data.dart +++ b/lib/core/backup/recurring_backup_data.dart @@ -29,10 +29,25 @@ class RecurringBackupData { Map toJson() => { 'definitions': definitions.map((r) => r.toJson()).toList(), 'steps': steps.map((r) => r.toJson()).toList(), - 'segments': segments.map((r) => r.toJson()).toList(), + 'segments': segments.map((r) => _portableSegment(r.toJson())).toList(), 'exclusions': exclusions.map((r) => r.toJson()).toList(), }; + static Map _portableSegment(Map source) => { + for (final key in [ + 'id', + 'seriesId', + 'ruleJson', + 'scheduleJson', + 'preparationId', + 'fromSlot', + 'beforeSlot', + 'createdAt', + 'preparationNotBefore', + ]) + key: source[key], + }; + factory RecurringBackupData.fromJson(Object? value) { try { final map = value as Map; @@ -43,7 +58,10 @@ class RecurringBackupData { return RecurringBackupData( definitions: rows('definitions', PreparationDefinition.fromJson), steps: rows('steps', PreparationDefinitionStep.fromJson), - segments: rows('segments', RecurringScheduleSegment.fromJson), + segments: rows( + 'segments', + (json) => RecurringScheduleSegment.fromJson(_portableSegment(json)), + ), exclusions: rows('exclusions', RecurringScheduleExclusion.fromJson), ); } catch (_) { diff --git a/lib/core/database/database.dart b/lib/core/database/database.dart index 00383acb..723f84e4 100644 --- a/lib/core/database/database.dart +++ b/lib/core/database/database.dart @@ -1,4 +1,5 @@ import 'package:drift/drift.dart'; +import 'package:on_time_front/core/database/schedule_aggregate_schema.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/core/database/installation_key_store.dart'; import 'package:on_time_front/core/database/open_database.dart'; @@ -52,30 +53,59 @@ class AppDatabase extends _$AppDatabase { AppDatabase.forTesting(super.e); @override - int get schemaVersion => 2; + int get schemaVersion => 3; @override MigrationStrategy get migration => MigrationStrategy( onCreate: (Migrator m) async { await m.createAll(); + await installAggregateConcurrency(this); }, onUpgrade: (Migrator m, int from, int to) async { - if (from < 1 || to > schemaVersion) { + if (from < 1 || from > schemaVersion || to > schemaVersion) { throw StateError( 'Only the encrypted local-only schema can be upgraded.', ); } - if (from < 2) { - await m.createTable(preparationDefinitions); - await m.createTable(preparationDefinitionSteps); - await m.createTable(recurringScheduleSegments); - await m.createTable(recurringScheduleExclusions); - await m.addColumn(schedules, schedules.recurringSegmentId); - await m.addColumn(schedules, schedules.recurringSlotKey); - await m.addColumn(schedules, schedules.recurringOrdinal); - await m.addColumn(schedules, schedules.recurringOverrides); - await m.addColumn(schedules, schedules.preparationDefinitionId); - } + await transaction(() async { + if (from < 2) { + await m.createTable(preparationDefinitions); + await m.createTable(preparationDefinitionSteps); + await m.createTable(recurringScheduleSegments); + await m.createTable(recurringScheduleExclusions); + await m.addColumn(schedules, schedules.recurringSegmentId); + await m.addColumn(schedules, schedules.recurringSlotKey); + await m.addColumn(schedules, schedules.recurringOrdinal); + await m.addColumn(schedules, schedules.recurringOverrides); + await m.addColumn(schedules, schedules.preparationDefinitionId); + } + if (from < 3) { + await m.addColumn(users, users.storeIncarnation); + for (final column in [ + schedules.aggregateIncarnation, + schedules.aggregateVersion, + schedules.lastMutationId, + schedules.lastMutationDigest, + schedules.lastMutationVersion, + ]) { + await m.addColumn(schedules, column); + } + // Schema 1 creates the latest recurring tables above. + if (from >= 2) { + for (final column in [ + recurringScheduleSegments.rootSegmentId, + recurringScheduleSegments.aggregateIncarnation, + recurringScheduleSegments.aggregateVersion, + recurringScheduleSegments.lastMutationId, + recurringScheduleSegments.lastMutationDigest, + recurringScheduleSegments.lastMutationVersion, + ]) { + await m.addColumn(recurringScheduleSegments, column); + } + } + await installAggregateConcurrency(this); + } + }); }, beforeOpen: (details) async { await customStatement('PRAGMA foreign_keys = ON'); diff --git a/lib/core/database/schedule_aggregate_schema.dart b/lib/core/database/schedule_aggregate_schema.dart new file mode 100644 index 00000000..2f831dae --- /dev/null +++ b/lib/core/database/schedule_aggregate_schema.dart @@ -0,0 +1,119 @@ +import 'package:on_time_front/core/database/database.dart'; + +/// Metadata belongs to the aggregate, not a global mutation log. SQL triggers +/// also observe legacy writers; merely materializing a new occurrence is not an +/// edit of an existing aggregate. None of these fields enter portable backups. +Future installAggregateConcurrency(AppDatabase db) async { + for (final table in ['schedules', 'recurring_schedule_segments']) { + await db.customStatement( + 'UPDATE $table SET aggregate_incarnation = lower(hex(randomblob(16))), aggregate_version = 0 WHERE aggregate_incarnation IS NULL', + ); + await db.customStatement( + '''CREATE TRIGGER ${table}_identity AFTER INSERT ON $table + WHEN NEW.aggregate_incarnation IS NULL BEGIN + UPDATE $table SET aggregate_incarnation = lower(hex(randomblob(16))), aggregate_version = 0 WHERE id = NEW.id; END''', + ); + } + await db.customStatement( + 'UPDATE users SET store_incarnation = lower(hex(randomblob(16))) WHERE store_incarnation IS NULL', + ); + await db.customStatement( + '''CREATE TRIGGER users_identity AFTER INSERT ON users WHEN NEW.store_incarnation IS NULL BEGIN + UPDATE users SET store_incarnation = lower(hex(randomblob(16))) WHERE id = NEW.id; END''', + ); + await db.customStatement( + "UPDATE recurring_schedule_segments SET root_segment_id = (SELECT r.id FROM recurring_schedule_segments r WHERE r.series_id = recurring_schedule_segments.series_id ORDER BY r.created_at,r.id LIMIT 1) WHERE root_segment_id IS NULL", + ); + await db.customStatement( + "CREATE TRIGGER segment_root AFTER INSERT ON recurring_schedule_segments WHEN NEW.root_segment_id IS NULL BEGIN UPDATE recurring_schedule_segments SET root_segment_id = COALESCE((SELECT root_segment_id FROM recurring_schedule_segments WHERE series_id=NEW.series_id AND root_segment_id IS NOT NULL LIMIT 1), NEW.id) WHERE id=NEW.id; END", + ); + const scheduleFields = [ + 'place_id', + 'schedule_name', + 'time_zone_id', + 'occurrence_offset_seconds', + 'schedule_time', + 'move_time', + 'is_changed', + 'is_started', + 'schedule_spare_time', + 'schedule_note', + 'lateness_time', + 'done_status', + 'started_at', + 'finished_at', + 'preparation_mode', + 'preparation_template_id', + 'preparation_template_name', + 'preparation_template_deleted', + 'preparation_frozen', + 'score_contribution_recorded', + 'recurring_segment_id', + 'recurring_slot_key', + 'recurring_ordinal', + 'recurring_overrides', + 'preparation_definition_id', + ]; + String changed(List fields) => + fields.map((f) => 'NEW.$f IS NOT OLD.$f').join(' OR '); + String rootFor(String segment) => + 'id = (SELECT root_segment_id FROM recurring_schedule_segments WHERE id = $segment)'; + String invalidateRoots(String schedulePredicate) => + 'UPDATE recurring_schedule_segments SET aggregate_version=COALESCE(aggregate_version,0)+1 WHERE id IN (SELECT r.root_segment_id FROM recurring_schedule_segments r JOIN schedules s ON s.recurring_segment_id=r.id WHERE $schedulePredicate);'; + await db.customStatement( + '''CREATE TRIGGER schedules_version AFTER UPDATE OF ${scheduleFields.join(',')} ON schedules + WHEN ${changed(scheduleFields)} BEGIN + UPDATE schedules SET aggregate_version = COALESCE(aggregate_version,0)+1 WHERE id = NEW.id; + UPDATE recurring_schedule_segments SET aggregate_version = COALESCE(aggregate_version,0)+1 WHERE ${rootFor('OLD.recurring_segment_id')}; END''', + ); + await db.customStatement( + '''CREATE TRIGGER schedules_deleted AFTER DELETE ON schedules BEGIN + UPDATE recurring_schedule_segments SET aggregate_version = COALESCE(aggregate_version,0)+1 WHERE ${rootFor('OLD.recurring_segment_id')}; END''', + ); + const segmentFields = [ + 'rule_json', + 'schedule_json', + 'preparation_id', + 'from_slot', + 'before_slot', + 'preparation_not_before', + ]; + await db.customStatement( + '''CREATE TRIGGER segments_version AFTER UPDATE OF ${segmentFields.join(',')} ON recurring_schedule_segments + WHEN ${changed(segmentFields)} BEGIN + UPDATE recurring_schedule_segments SET aggregate_version = COALESCE(aggregate_version,0)+1 WHERE ${rootFor('NEW.id')}; END''', + ); + for (final event in ['INSERT', 'UPDATE', 'DELETE']) { + final row = event == 'DELETE' ? 'OLD' : 'NEW'; + await db.customStatement( + 'CREATE TRIGGER exclusion_${event.toLowerCase()} AFTER $event ON recurring_schedule_exclusions BEGIN UPDATE recurring_schedule_segments SET aggregate_version=COALESCE(aggregate_version,0)+1 WHERE ${rootFor('$row.segment_id')}; END', + ); + await db.customStatement( + '''CREATE TRIGGER preparation_schedule_${event.toLowerCase()} AFTER $event ON preparation_schedules BEGIN + UPDATE schedules SET aggregate_version = COALESCE(aggregate_version,0)+1 WHERE id = $row.schedule_id; + UPDATE recurring_schedule_segments SET aggregate_version = COALESCE(aggregate_version,0)+1 WHERE ${rootFor('(SELECT recurring_segment_id FROM schedules WHERE id = $row.schedule_id)')}; END''', + ); + await db.customStatement( + '''CREATE TRIGGER preparation_default_${event.toLowerCase()} AFTER $event ON preparation_users BEGIN + UPDATE schedules SET aggregate_version = COALESCE(aggregate_version,0)+1 WHERE preparation_definition_id IS NULL AND NOT EXISTS (SELECT 1 FROM preparation_schedules p WHERE p.schedule_id=schedules.id) AND NOT EXISTS (SELECT 1 FROM preparation_templates p WHERE p.id=schedules.preparation_template_id AND schedules.preparation_template_deleted=0); ${invalidateRoots('s.preparation_definition_id IS NULL AND NOT EXISTS (SELECT 1 FROM preparation_schedules p WHERE p.schedule_id=s.id) AND NOT EXISTS (SELECT 1 FROM preparation_templates p WHERE p.id=s.preparation_template_id AND s.preparation_template_deleted=0)')} END''', + ); + } + for (final event in ['INSERT', 'UPDATE', 'DELETE']) { + final row = event == 'DELETE' ? 'OLD' : 'NEW'; + await db.customStatement( + "CREATE TRIGGER template_steps_${event.toLowerCase()} AFTER $event ON preparation_template_steps BEGIN UPDATE schedules SET aggregate_version=COALESCE(aggregate_version,0)+1 WHERE preparation_template_id=$row.template_id; ${invalidateRoots('s.preparation_template_id=$row.template_id')} END", + ); + await db.customStatement( + "CREATE TRIGGER definition_steps_${event.toLowerCase()} AFTER $event ON preparation_definition_steps BEGIN UPDATE schedules SET aggregate_version=COALESCE(aggregate_version,0)+1 WHERE preparation_definition_id=$row.definition_id; ${invalidateRoots('s.preparation_definition_id=$row.definition_id')} UPDATE recurring_schedule_segments SET aggregate_version=COALESCE(aggregate_version,0)+1 WHERE id IN (SELECT root_segment_id FROM recurring_schedule_segments WHERE preparation_id=$row.definition_id); END", + ); + } + for (final event in ['UPDATE', 'DELETE']) { + await db.customStatement( + "CREATE TRIGGER template_${event.toLowerCase()} AFTER $event ON preparation_templates BEGIN UPDATE schedules SET aggregate_version=COALESCE(aggregate_version,0)+1 WHERE preparation_template_id=OLD.id; ${invalidateRoots('s.preparation_template_id=OLD.id')} END", + ); + } + await db.customStatement( + '''CREATE TRIGGER place_version AFTER UPDATE OF place_name ON places WHEN NEW.place_name IS NOT OLD.place_name BEGIN + UPDATE schedules SET aggregate_version = COALESCE(aggregate_version,0)+1 WHERE place_id = NEW.id; ${invalidateRoots('s.place_id=NEW.id')} END''', + ); +} diff --git a/lib/data/daos/schedule_dao.dart b/lib/data/daos/schedule_dao.dart index a656b28b..d091e938 100644 --- a/lib/data/daos/schedule_dao.dart +++ b/lib/data/daos/schedule_dao.dart @@ -22,13 +22,12 @@ class ScheduleDao extends DatabaseAccessor await into( db.places, ).insertOnConflictUpdate(scheduleWithPlace.place.toCompanion(false)); - final scheduleModel = await into( - db.schedules, - ).insertReturning(scheduleWithPlace.schedule.toCompanion(false)); - return ScheduleWithPlace( - schedule: scheduleModel, - place: scheduleWithPlace.place, + await into(db.schedules).insert( + _withoutConcurrencyMetadata( + scheduleWithPlace.schedule.toCompanion(false), + ), ); + return getScheduleById(scheduleWithPlace.schedule.id); }); } @@ -53,14 +52,26 @@ class ScheduleDao extends DatabaseAccessor } } - Future updateSchedule(Schedule scheduleModel) async { - final scheduleList = - await (update(db.schedules) - ..where((tbl) => tbl.id.equals(scheduleModel.id))) - .writeReturning(scheduleModel.toCompanion(true)); - assert(scheduleList.length == 1); - return scheduleList.first; - } + SchedulesCompanion _withoutConcurrencyMetadata(SchedulesCompanion values) => + values.copyWith( + aggregateIncarnation: const Value.absent(), + aggregateVersion: const Value.absent(), + lastMutationId: const Value.absent(), + lastMutationDigest: const Value.absent(), + lastMutationVersion: const Value.absent(), + ); + + Future updateSchedule(Schedule scheduleModel) => + transaction(() async { + final changed = + await (update( + db.schedules, + )..where((t) => t.id.equals(scheduleModel.id))).write( + _withoutConcurrencyMetadata(scheduleModel.toCompanion(true)), + ); + if (changed != 1) throw ScheduleNotFound(scheduleModel.id); + return (await getScheduleById(scheduleModel.id)).schedule; + }); Future updateScheduleWithPlace( ScheduleWithPlace value, @@ -71,7 +82,7 @@ class ScheduleDao extends DatabaseAccessor ).insertOnConflictUpdate(value.place.toCompanion(false)); await (update(db.schedules) ..where((table) => table.id.equals(value.schedule.id))) - .write(value.schedule.toCompanion(true)); + .write(_withoutConcurrencyMetadata(value.schedule.toCompanion(true))); return getScheduleById(value.schedule.id); }); } @@ -129,17 +140,37 @@ class ScheduleDao extends DatabaseAccessor } Stream> watchScheduleList() { - final query = select(db.schedules).join([ - leftOuterJoin(db.places, db.places.id.equalsExp(db.schedules.placeId)), - ])..orderBy([OrderingTerm.asc(db.schedules.scheduleTime)]); - return query.watch().map( - (rows) => [ - for (final row in rows) - ScheduleWithPlace( - schedule: row.readTable(db.schedules), - place: row.readTable(db.places), - ), - ], - ); + // Trigger-written versions also change when a preparation dependency changes. + // Drift needs those dependencies explicitly; notifications remain commit-bound. + return db + .customSelect( + 'SELECT s.*, p.id AS joined_place_id, p.place_name AS joined_place_name FROM schedules s JOIN places p ON p.id=s.place_id ORDER BY s.schedule_time ASC', + readsFrom: { + db.schedules, + db.places, + db.preparationSchedules, + db.preparationUsers, + db.preparationTemplates, + db.preparationTemplateSteps, + db.preparationDefinitions, + db.preparationDefinitionSteps, + db.recurringScheduleSegments, + db.recurringScheduleExclusions, + }, + ) + .watch() + .map( + (rows) => rows + .map( + (row) => ScheduleWithPlace( + schedule: db.schedules.map(row.data), + place: Place( + id: row.read('joined_place_id'), + placeName: row.read('joined_place_name'), + ), + ), + ) + .toList(growable: false), + ); } } diff --git a/lib/data/repositories/alarm_repository_impl.dart b/lib/data/repositories/alarm_repository_impl.dart index f96617f9..bb0b015e 100644 --- a/lib/data/repositories/alarm_repository_impl.dart +++ b/lib/data/repositories/alarm_repository_impl.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/data/repositories/schedule_aggregate_repository_impl.dart'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/data/mappers/domain_persistence_mappers.dart'; import 'package:on_time_front/domain/repositories/recurring_schedule_repository.dart'; @@ -20,15 +21,11 @@ class AlarmRepositoryImpl implements AlarmRepository { RecurringScheduleRepository? recurringScheduleRepository, }) : _database = database, _recurring = recurringScheduleRepository, - _userDao = database.userDao, - _scheduleRepository = scheduleRepository, - _preparationRepository = preparationRepository; + _userDao = database.userDao; final AppDatabase _database; final RecurringScheduleRepository? _recurring; final UserDao _userDao; - final ScheduleRepository _scheduleRepository; - final PreparationRepository _preparationRepository; @override Future getAlarmSettings() async { @@ -60,31 +57,22 @@ class AlarmRepositoryImpl implements AlarmRepository { // a bounded pending set. Supply the nearest candidates from each series. final civilStart = startDate.subtract(const Duration(days: 2)); await _recurring?.materialize(civilStart, endDate, perSeriesLimit: 64); - final schedules = _recurring == null - ? await _scheduleRepository.getSchedulesByDate(startDate, endDate) - : (await _database.scheduleDao.getSchedulesByDate( - civilStart, - endDate.add(const Duration(days: 2)), - )).map((r) => r.toScheduleEntity()).toList(); - final defaultPreparation = await _preparationRepository - .getDefualtPreparation(); - final result = []; - for (final schedule in schedules) { - await _preparationRepository.getPreparationByScheduleId(schedule.id); - final custom = await _preparationRepository.preparationStream.first.then( - (preparations) => preparations[schedule.id], - ); - result.add( - ScheduleWithPreparationEntity.fromScheduleAndPreparationEntity( - schedule, - PreparationWithTimeEntity.fromPreparation( - custom == null || custom.preparationStepList.isEmpty - ? defaultPreparation - : custom, + return _database.transaction(() async { + final schedules = (await _database.scheduleDao.getSchedulesByDate( + _recurring == null ? startDate : civilStart, + _recurring == null ? endDate : endDate.add(const Duration(days: 2)), + )).map((r) => r.toScheduleEntity()); + final result = []; + for (final schedule in schedules) { + final preparation = await readSchedulePreparation(_database, schedule); + result.add( + ScheduleWithPreparationEntity.fromScheduleAndPreparationEntity( + schedule, + PreparationWithTimeEntity.fromPreparation(preparation), ), - ), - ); - } - return result; + ); + } + return List.unmodifiable(result); + }); } } diff --git a/lib/data/repositories/preparation_repository_impl.dart b/lib/data/repositories/preparation_repository_impl.dart index 7b17d994..7b936b78 100644 --- a/lib/data/repositories/preparation_repository_impl.dart +++ b/lib/data/repositories/preparation_repository_impl.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:injectable/injectable.dart'; import 'package:on_time_front/core/constants/local_profile.dart'; import 'package:on_time_front/core/database/database.dart'; @@ -17,8 +18,39 @@ class PreparationRepositoryImpl implements PreparationRepository { }) : _localDataSource = preparationLocalDataSource, _userRepository = userRepository, _userDao = database.userDao, - _database = database; + _database = database { + _subscription = database + .customSelect( + 'SELECT count(*) AS n FROM schedules', + readsFrom: { + database.schedules, + database.preparationSchedules, + database.preparationDefinitions, + database.preparationDefinitionSteps, + database.preparationUsers, + database.preparationTemplates, + database.preparationTemplateSteps, + }, + ) + .watch() + .asyncMap( + (_) => database.transaction(() async { + final schedules = await database.select(database.schedules).get(); + return { + for (final schedule in schedules) + schedule.id: await _localDataSource.getPreparationByScheduleId( + schedule.id, + ), + }; + }), + ) + .listen( + _preparationStreamController.add, + onError: _preparationStreamController.addError, + ); + } + late final StreamSubscription> _subscription; final PreparationLocalDataSource _localDataSource; final UserRepository _userRepository; final UserDao _userDao; @@ -62,12 +94,14 @@ class PreparationRepositoryImpl implements PreparationRepository { PreparationEntity preparationEntity, String scheduleId, ) async { - await _localDataSource.createCustomPreparation( - preparationEntity, - scheduleId, - ); + await _database.transaction(() async { + await _localDataSource.createCustomPreparation( + preparationEntity, + scheduleId, + ); + await _userDao.markDurableDataChanged(localProfileId); + }); _emitSchedulePreparation(scheduleId, preparationEntity); - await _userDao.markDurableDataChanged(localProfileId); } @override @@ -99,12 +133,14 @@ class PreparationRepositoryImpl implements PreparationRepository { PreparationEntity preparationEntity, String scheduleId, ) async { - await _localDataSource.replaceSchedulePreparation( - preparationEntity, - scheduleId: scheduleId, - ); + await _database.transaction(() async { + await _localDataSource.replaceSchedulePreparation( + preparationEntity, + scheduleId: scheduleId, + ); + await _userDao.markDurableDataChanged(localProfileId); + }); _emitSchedulePreparation(scheduleId, preparationEntity); - await _userDao.markDurableDataChanged(localProfileId); } @override @@ -136,5 +172,8 @@ class PreparationRepositoryImpl implements PreparationRepository { }); } - Future dispose() => _preparationStreamController.close(); + Future dispose() async { + await _subscription.cancel(); + await _preparationStreamController.close(); + } } diff --git a/lib/data/repositories/recurring_schedule_repository_impl.dart b/lib/data/repositories/recurring_schedule_repository_impl.dart index 34bf7ce3..bb99c955 100644 --- a/lib/data/repositories/recurring_schedule_repository_impl.dart +++ b/lib/data/repositories/recurring_schedule_repository_impl.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'package:on_time_front/domain/entities/place_entity.dart'; import 'package:on_time_front/core/time/civil_time_resolver.dart'; import 'dart:convert'; @@ -555,7 +556,9 @@ class RecurringScheduleRepositoryImpl implements RecurringScheduleRepository { final existing = await (db.select( db.recurringScheduleSegments, )..where((t) => t.seriesId.equals(schedule.id))).get(); - if (existing.isNotEmpty) return; + if (existing.isNotEmpty) { + throw const ScheduleSaveRejected(ScheduleSaveFailure.conflict); + } final cutoff = _now(); final check = await _review(schedule, preparation, rule, cutoff: cutoff); _requireClear(check, excludedSlots); diff --git a/lib/data/repositories/schedule_aggregate_repository_impl.dart b/lib/data/repositories/schedule_aggregate_repository_impl.dart new file mode 100644 index 00000000..4de689b9 --- /dev/null +++ b/lib/data/repositories/schedule_aggregate_repository_impl.dart @@ -0,0 +1,632 @@ +import 'dart:convert'; +import 'package:crypto/crypto.dart'; +import 'package:drift/drift.dart'; +import 'package:injectable/injectable.dart'; +import 'package:on_time_front/core/constants/local_profile.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/data/data_sources/preparation_local_data_source.dart'; +import 'package:on_time_front/data/mappers/domain_persistence_mappers.dart'; +import 'package:on_time_front/data/mappers/recurrence_codec.dart'; +import 'package:on_time_front/domain/entities/place_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_step_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; +import 'package:on_time_front/domain/recurrence/recurring_schedule.dart'; +import 'package:on_time_front/domain/repositories/recurring_schedule_repository.dart'; +import 'package:on_time_front/domain/repositories/schedule_aggregate_repository.dart'; +import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; +import 'package:uuid/uuid.dart'; + +@Singleton(as: ScheduleAggregateRepository) +class ScheduleAggregateRepositoryImpl implements ScheduleAggregateRepository { + ScheduleAggregateRepositoryImpl( + this.db, + this.recurring, { + @ignoreParam LocalDataOperationGate? gate, + @ignoreParam DateTime Function()? now, + }) : gate = gate ?? LocalDataOperationGate.shared, + now = now ?? DateTime.now; + final AppDatabase db; + final RecurringScheduleRepository recurring; + final LocalDataOperationGate gate; + final DateTime Function() now; + static const uuid = Uuid(); + + @override + bool isCurrent(ScheduleSaveReceipt receipt) => + !gate.isInvalidated && + !gate.isReplacingData && + gate.generation == receipt.generation; + Future _profile() => (db.select( + db.users, + )..where((t) => t.id.equals(localProfileId))).getSingle(); + @override + Future newBaseline() => db.transaction(() async { + final profile = await _profile(); + return ScheduleEditBaseline( + store: profile.storeIncarnation!, + generation: gate.generation, + revision: profile.dataRevision, + ); + }); + @override + Future<({ScheduleEditBaseline baseline, PreparationEntity preparation})> + newDraft() => db.transaction(() async { + final baseline = await newBaseline(); + final preparation = await db.preparationUserDao.getPreparationUsersByUserId( + localProfileId, + ); + return (baseline: baseline, preparation: preparation); + }); + Future _root( + String? segmentId, { + String? seriesId, + }) async { + if (segmentId == null && seriesId == null) return null; + final source = segmentId != null + ? await (db.select( + db.recurringScheduleSegments, + )..where((t) => t.id.equals(segmentId))).getSingleOrNull() + : await (db.select(db.recurringScheduleSegments) + ..where((t) => t.seriesId.equals(seriesId!)) + ..limit(1)) + .getSingleOrNull(); + if (source?.rootSegmentId == null) return null; + return (db.select( + db.recurringScheduleSegments, + )..where((t) => t.id.equals(source!.rootSegmentId!))).getSingleOrNull(); + } + + Future preparationFor(ScheduleEntity schedule) => + readSchedulePreparation(db, schedule); + @override + Future readForEdit(String id) => + db.transaction(() async { + final row = await db.scheduleDao.getScheduleById(id); + final schedule = row.toScheduleEntity(); + final root = await _root(schedule.recurringSegmentId); + final profile = await _profile(); + final preparation = await preparationFor(schedule); + return ScheduleEditSnapshot( + schedule, + preparation, + ScheduleEditBaseline( + store: profile.storeIncarnation!, + generation: gate.generation, + revision: profile.dataRevision, + incarnation: row.schedule.aggregateIncarnation, + version: row.schedule.aggregateVersion, + rootId: root?.id, + rootIncarnation: root?.aggregateIncarnation, + rootVersion: root?.aggregateVersion, + ), + segment: schedule.recurringSegmentId == null + ? null + : await recurring.getSegment(schedule.recurringSegmentId!), + ); + }); + Never _reject(ScheduleSaveFailure value) => throw ScheduleSaveRejected(value); + List _steps(PreparationEntity p) => p.ordered.preparationStepList + .map((s) => [s.preparationName, s.preparationTime.inMinutes]) + .toList(); + SchedulePreparationMode _mode(ScheduleFormSubmission value) => + value.schedule.preparationMode ?? + (value.preparationChanged + ? SchedulePreparationMode.custom + : SchedulePreparationMode.defaultPreparation); + List _editable(ScheduleEntity s, SchedulePreparationMode mode) => [ + s.scheduleName, + s.place.placeName, + s.scheduleTime.toIso8601String(), + s.timeZoneId, + s.occurrenceOffsetSeconds, + s.moveTime.inMicroseconds, + s.scheduleSpareTime?.inMicroseconds, + s.scheduleNote, + mode.name, + s.preparationTemplateId, + ]; + String _digest(ScheduleFormSubmission v, bool editing) => sha256 + .convert( + utf8.encode( + jsonEncode([ + editing, + v.baseline?.props, + v.schedule.id, + _editable(v.schedule, _mode(v)), + _steps(v.preparation), + v.recurrenceRule == null + ? null + : RecurrenceCodec.ruleToJson(v.recurrenceRule!), + v.recurringScope.name, + v.recurrenceCountChanged, + (v.excludedSlots.toList()..sort()), + v.confirmDetached, + v.reviewedFirstSlotKey, + ]), + ), + ) + .toString(); + bool _receipt( + String? id, + String? digest, + int? version, + int? receiptVersion, + String wantedId, + String wantedDigest, + ) => id == wantedId && digest == wantedDigest && version == receiptVersion; + void _validate(ScheduleFormSubmission v) { + final s = v.schedule; + if (s.scheduleName.trim().isEmpty || + s.place.placeName.trim().isEmpty || + s.moveTime.isNegative || + (s.scheduleSpareTime?.isNegative ?? false)) { + _reject(ScheduleSaveFailure.invalid); + } + final mode = _mode(v); + if (mode == SchedulePreparationMode.template && + s.preparationTemplateId == null) { + _reject(ScheduleSaveFailure.invalid); + } + if (mode != SchedulePreparationMode.template && + s.preparationTemplateId != null) { + _reject(ScheduleSaveFailure.invalid); + } + if (mode == SchedulePreparationMode.custom || + v.recurrenceRule != null || + s.preparationDefinitionId != null) { + final steps = v.preparation.preparationStepList; + final ids = steps.map((s) => s.id).toSet(); + final targets = steps + .map((s) => s.nextPreparationId) + .whereType() + .toList(); + if (targets.any((id) => !ids.contains(id)) || + targets.toSet().length != targets.length) { + _reject(ScheduleSaveFailure.invalid); + } + for (final start in steps) { + final seen = {}; + var node = start; + while (true) { + if (!seen.add(node.id)) _reject(ScheduleSaveFailure.invalid); + if (node.nextPreparationId == null) break; + node = steps.firstWhere((s) => s.id == node.nextPreparationId); + } + } + + if (steps.isEmpty || + steps.map((s) => s.id).toSet().length != steps.length || + steps.any( + (s) => + s.preparationName.trim().isEmpty || + s.preparationName.length > 30 || + s.preparationTime.inMicroseconds % + Duration.microsecondsPerMinute != + 0 || + s.preparationTime.inMinutes < 1 || + s.preparationTime.inMinutes > 1440, + )) { + _reject(ScheduleSaveFailure.invalid); + } + } + } + + @override + Future save( + ScheduleFormSubmission v, { + required bool editing, + }) => db.transaction(() async { + final baseline = v.baseline, mutation = v.mutationId; + if (baseline == null || mutation == null || mutation.isEmpty) { + _reject(ScheduleSaveFailure.invalid); + } + if (gate.isInvalidated || gate.isReplacingData) { + _reject(ScheduleSaveFailure.unavailable); + } + final profile = await _profile(); + if (profile.storeIncarnation != baseline.store || + gate.generation != baseline.generation) { + _reject(ScheduleSaveFailure.conflict); + } + _validate(v); + if (!editing && + (v.schedule.isStarted || + v.schedule.startedAt != null || + v.schedule.finishedAt != null || + v.schedule.doneStatus != ScheduleDoneStatus.notEnded || + v.schedule.scoreContributionRecorded || + v.schedule.preparationFrozen || + v.schedule.recurringSlotKey != null || + v.schedule.recurringOrdinal != null || + v.schedule.recurringOverrides.isNotEmpty || + v.schedule.latenessTime != 0 || + v.schedule.recurringSegmentId != null || + v.schedule.preparationDefinitionId != null)) { + _reject(ScheduleSaveFailure.invalid); + } + final digest = _digest(v, editing); + final row = await (db.select( + db.schedules, + )..where((t) => t.id.equals(v.schedule.id))).getSingleOrNull(); + final recurringCreate = !editing && v.recurrenceRule != null; + final following = + editing && + baseline.rootId != null && + v.recurringScope == RecurringEditScope.following; + final root = await _root( + baseline.rootId ?? row?.recurringSegmentId, + seriesId: recurringCreate ? v.schedule.id : null, + ); + final rootReceipt = recurringCreate || following; + if (rootReceipt && + root != null && + _receipt( + root.lastMutationId, + root.lastMutationDigest, + root.aggregateVersion, + root.lastMutationVersion, + mutation, + digest, + )) { + if (editing && root.aggregateIncarnation != baseline.rootIncarnation) { + _reject(ScheduleSaveFailure.conflict); + } + return ScheduleSaveReceipt( + scheduleId: v.schedule.id, + mutationId: mutation, + generation: gate.generation, + changed: false, + ); + } + if (!rootReceipt && + row != null && + _receipt( + row.lastMutationId, + row.lastMutationDigest, + row.aggregateVersion, + row.lastMutationVersion, + mutation, + digest, + )) { + if (editing && row.aggregateIncarnation != baseline.incarnation) { + _reject(ScheduleSaveFailure.conflict); + } + return ScheduleSaveReceipt( + scheduleId: v.schedule.id, + mutationId: mutation, + generation: gate.generation, + changed: false, + ); + } + if (editing) { + if (row == null || + row.aggregateIncarnation != baseline.incarnation || + row.aggregateVersion != baseline.version || + root?.aggregateIncarnation != baseline.rootIncarnation || + root?.aggregateVersion != baseline.rootVersion) { + _reject(ScheduleSaveFailure.conflict); + } + final current = (await db.scheduleDao.getScheduleById( + row.id, + )).toScheduleEntity(); + final prep = await preparationFor(current); + final lead = + prep.totalDuration + + current.moveTime + + (current.scheduleSpareTime ?? Duration.zero); + if (current.isStarted || + current.preparationFrozen || + current.doneStatus != ScheduleDoneStatus.notEnded || + !current.occurrenceInstantUtc.subtract(lead).isAfter(now().toUtc())) { + _reject(ScheduleSaveFailure.protected); + } + } else if (row != null || + root != null || + profile.dataRevision != baseline.revision) { + _reject(ScheduleSaveFailure.conflict); + } + final priorOwnedDefinitions = { + if (row?.preparationDefinitionId != null) row!.preparationDefinitionId!, + }; + if (following && root != null) { + final parts = await (db.select( + db.recurringScheduleSegments, + )..where((t) => t.seriesId.equals(root.seriesId))).get(); + priorOwnedDefinitions.addAll(parts.map((p) => p.preparationId)); + final occurrences = await (db.select( + db.schedules, + )..where((t) => t.recurringSegmentId.isIn(parts.map((p) => p.id)))).get(); + priorOwnedDefinitions.addAll( + occurrences.map((s) => s.preparationDefinitionId).whereType(), + ); + } + var changed = true; + if (recurringCreate) { + await recurring.create( + v.schedule, + v.preparation, + v.recurrenceRule!, + excludedSlots: v.excludedSlots, + reviewedFirstSlotKey: v.reviewedFirstSlotKey, + ); + } else if (editing && row?.preparationDefinitionId != null) { + final current = (await db.scheduleDao.getScheduleById( + v.schedule.id, + )).toScheduleEntity(); + final prep = await preparationFor(current); + final same = + jsonEncode(_editable(current, SchedulePreparationMode.custom)) == + jsonEncode( + _editable(v.schedule, SchedulePreparationMode.custom), + ) && + jsonEncode(_steps(prep)) == jsonEncode(_steps(v.preparation)); + if (same && (!following || await _sameFollowing(v, current))) { + changed = false; + } else if (following) { + await recurring.updateFollowing( + current, + v.schedule, + v.preparation, + v.recurrenceRule!, + countChanged: v.recurrenceCountChanged, + excludedSlots: v.excludedSlots, + reviewedFirstSlotKey: v.reviewedFirstSlotKey, + confirmDetached: v.confirmDetached, + ); + } else { + await recurring.updateOccurrence( + current, + v.schedule, + v.preparation, + preparationChanged: + jsonEncode(_steps(prep)) != jsonEncode(_steps(v.preparation)), + ); + } + } else { + changed = await _saveOrdinary(v, editing: editing); + if (changed) await db.userDao.markDurableDataChanged(localProfileId); + } + if (changed) { + for (final candidate in priorOwnedDefinitions) { + await _collectUnusedOwnedDefinition(candidate); + } + } + // There are no OS/plugin awaits in this transaction. Recheck replacement + // currentness immediately before returning its durable commit receipt. + if (gate.generation != baseline.generation || + gate.isReplacingData || + gate.isInvalidated) { + _reject(ScheduleSaveFailure.conflict); + } + if (rootReceipt) { + final target = await _root( + baseline.rootId, + seriesId: recurringCreate ? v.schedule.id : null, + ); + if (target == null) _reject(ScheduleSaveFailure.conflict); + await (db.update( + db.recurringScheduleSegments, + )..where((t) => t.id.equals(target.id))).write( + RecurringScheduleSegmentsCompanion( + lastMutationId: Value(mutation), + lastMutationDigest: Value(digest), + lastMutationVersion: Value(target.aggregateVersion), + ), + ); + } else { + final target = await (db.select( + db.schedules, + )..where((t) => t.id.equals(v.schedule.id))).getSingle(); + await (db.update( + db.schedules, + )..where((t) => t.id.equals(target.id))).write( + SchedulesCompanion( + lastMutationId: Value(mutation), + lastMutationDigest: Value(digest), + lastMutationVersion: Value(target.aggregateVersion), + ), + ); + } + return ScheduleSaveReceipt( + scheduleId: v.schedule.id, + mutationId: mutation, + generation: gate.generation, + changed: changed, + ); + }); + + Future _sameFollowing( + ScheduleFormSubmission v, + ScheduleEntity current, + ) async { + if (v.recurrenceCountChanged || + v.excludedSlots.isNotEmpty || + v.recurrenceRule == null) { + return false; + } + final segment = await recurring.getSegment(current.recurringSegmentId!); + final parts = await (db.select( + db.recurringScheduleSegments, + )..where((t) => t.seriesId.equals(segment.seriesId))).get(); + if (segment.beforeSlot != null || + parts.any( + (p) => + p.id != segment.id && + DateTime.parse(p.fromSlot).compareTo(segment.fromSlot) > 0, + )) { + return false; + } + final expected = segment.rule.withStartAndEnd( + start: v.schedule.scheduleTime, + count: segment.rule.count, + until: segment.rule.until, + ); + return jsonEncode(RecurrenceCodec.ruleToJson(expected)) == + jsonEncode(RecurrenceCodec.ruleToJson(v.recurrenceRule!)); + } + + Future _collectUnusedOwnedDefinition(String candidate) async { + final unused = await db + .customSelect( + "SELECT id FROM preparation_definitions d WHERE id=? AND scope IN ('recurring','occurrence') AND NOT EXISTS(SELECT 1 FROM schedules s WHERE s.preparation_definition_id=d.id) AND NOT EXISTS(SELECT 1 FROM recurring_schedule_segments r WHERE r.preparation_id=d.id)", + variables: [Variable(candidate)], + ) + .get(); + if (unused.isEmpty) return; + await (db.delete( + db.preparationDefinitionSteps, + )..where((t) => t.definitionId.equals(candidate))).go(); + await (db.delete( + db.preparationDefinitions, + )..where((t) => t.id.equals(candidate))).go(); + } + + Future _saveOrdinary( + ScheduleFormSubmission v, { + required bool editing, + }) async { + final input = v.schedule, mode = _mode(v); + PreparationEntity desired = v.preparation; + if (mode == SchedulePreparationMode.template) { + final template = + await (db.select(db.preparationTemplates) + ..where((t) => t.id.equals(input.preparationTemplateId!))) + .getSingleOrNull(); + if (template == null) _reject(ScheduleSaveFailure.invalid); + desired = (await db.preparationTemplateDao.getById( + template.id, + )).preparation; + } else if (mode == SchedulePreparationMode.defaultPreparation) { + desired = await db.preparationUserDao.getPreparationUsersByUserId( + localProfileId, + ); + } + ScheduleEntity? current; + PreparationEntity? previous; + if (editing) { + current = (await db.scheduleDao.getScheduleById( + input.id, + )).toScheduleEntity(); + previous = await preparationFor(current); + final oldMode = + current.preparationMode ?? + (current.isChanged + ? SchedulePreparationMode.custom + : SchedulePreparationMode.defaultPreparation); + if (jsonEncode(_editable(current, oldMode)) == + jsonEncode(_editable(input, mode)) && + jsonEncode(_steps(previous)) == jsonEncode(_steps(desired))) { + return false; + } + } + final existingPlace = await (db.select( + db.places, + )..where((t) => t.id.equals(input.place.id))).getSingleOrNull(); + var place = input.place; + if (existingPlace != null && + existingPlace.placeName != input.place.placeName) { + place = PlaceEntity(id: uuid.v7(), placeName: input.place.placeName); + } + if (existingPlace == null || place.id != existingPlace.id) { + await db.into(db.places).insert(place.toPlaceRow().toCompanion(false)); + } + if (!editing) { + final created = ScheduleEntity( + id: input.id, + place: place, + scheduleName: input.scheduleName, + scheduleTime: input.scheduleTime, + timeZoneId: input.timeZoneId, + occurrenceOffsetSeconds: input.occurrenceOffsetSeconds, + moveTime: input.moveTime, + scheduleSpareTime: input.scheduleSpareTime, + scheduleNote: input.scheduleNote, + isStarted: false, + preparationTemplateId: input.preparationTemplateId, + preparationTemplateName: input.preparationTemplateName, + preparationMode: mode, + isChanged: mode != SchedulePreparationMode.defaultPreparation, + ); + await db + .into(db.schedules) + .insert(created.toScheduleRow().toCompanion(false)); + } else { + await (db.update( + db.schedules, + )..where((t) => t.id.equals(input.id))).write( + SchedulesCompanion( + placeId: Value(place.id), + scheduleName: Value(input.scheduleName), + scheduleTime: Value(input.scheduleTime), + timeZoneId: Value(input.timeZoneId), + occurrenceOffsetSeconds: Value(input.occurrenceOffsetSeconds), + moveTime: Value(input.moveTime), + scheduleSpareTime: Value(input.scheduleSpareTime), + scheduleNote: Value(input.scheduleNote), + isChanged: Value(mode != SchedulePreparationMode.defaultPreparation), + preparationMode: Value(mode.name), + preparationTemplateId: Value(input.preparationTemplateId), + preparationTemplateName: Value(input.preparationTemplateName), + preparationTemplateDeleted: const Value(false), + ), + ); + } + if (mode == SchedulePreparationMode.custom) { + final own = await db.preparationScheduleDao + .getPreparationSchedulesByScheduleId(input.id); + final old = own.ordered.preparationStepList; + final steps = desired.ordered.preparationStepList; + // Existing owned IDs survive semantic edits; a copied template/default + // never takes ownership of the source's IDs. + final ids = [ + for (var i = 0; i < steps.length; i++) + i < old.length ? old[i].id : uuid.v7(), + ]; + await db.preparationScheduleDao.createPreparationSchedule( + PreparationEntity( + preparationStepList: [ + for (var i = 0; i < steps.length; i++) + PreparationStepEntity( + id: ids[i], + preparationName: steps[i].preparationName, + preparationTime: steps[i].preparationTime, + nextPreparationId: i + 1 < ids.length ? ids[i + 1] : null, + ), + ], + ), + input.id, + ); + } else { + await (db.delete( + db.preparationSchedules, + )..where((t) => t.scheduleId.equals(input.id))).go(); + } + return true; + } +} + +Future readSchedulePreparation( + AppDatabase db, + ScheduleEntity schedule, +) async { + if (schedule.preparationMode == SchedulePreparationMode.template && + schedule.preparationTemplateId != null && + !schedule.preparationTemplateDeleted) { + final exists = + await (db.select(db.preparationTemplates) + ..where((t) => t.id.equals(schedule.preparationTemplateId!))) + .getSingleOrNull(); + if (exists != null) { + return (await db.preparationTemplateDao.getById(exists.id)).preparation; + } + } + final custom = await PreparationLocalDataSourceImpl( + appDatabase: db, + ).getPreparationByScheduleId(schedule.id); + if (custom.preparationStepList.isNotEmpty) return custom; + return db.preparationUserDao.getPreparationUsersByUserId(localProfileId); +} diff --git a/lib/data/tables/recurring_schedule_tables.dart b/lib/data/tables/recurring_schedule_tables.dart index 01f76958..70755173 100644 --- a/lib/data/tables/recurring_schedule_tables.dart +++ b/lib/data/tables/recurring_schedule_tables.dart @@ -29,6 +29,14 @@ class PreparationDefinitionSteps extends Table { } class RecurringScheduleSegments extends Table { + TextColumn get rootSegmentId => text().nullable()(); + // Installation-local concurrency metadata; excluded from portable backups. + TextColumn get aggregateIncarnation => text().nullable()(); + IntColumn get aggregateVersion => integer().nullable()(); + TextColumn get lastMutationId => text().nullable()(); + TextColumn get lastMutationDigest => text().nullable()(); + IntColumn get lastMutationVersion => integer().nullable()(); + TextColumn get id => text()(); TextColumn get seriesId => text()(); TextColumn get ruleJson => text()(); diff --git a/lib/data/tables/schedules_table.dart b/lib/data/tables/schedules_table.dart index d647b084..1a6baf7a 100644 --- a/lib/data/tables/schedules_table.dart +++ b/lib/data/tables/schedules_table.dart @@ -6,6 +6,13 @@ import 'package:uuid/uuid.dart'; @TableIndex(name: 'schedules_schedule_time_idx', columns: {#scheduleTime}) @TableIndex(name: 'schedules_place_id_idx', columns: {#placeId}) class Schedules extends Table { + // Installation-local concurrency metadata; excluded from portable backups. + TextColumn get aggregateIncarnation => text().nullable()(); + IntColumn get aggregateVersion => integer().nullable()(); + TextColumn get lastMutationId => text().nullable()(); + TextColumn get lastMutationDigest => text().nullable()(); + IntColumn get lastMutationVersion => integer().nullable()(); + TextColumn get id => text().clientDefault(() => Uuid().v7())(); TextColumn get placeId => text().references(Places, #id)(); TextColumn get scheduleName => text()(); diff --git a/lib/data/tables/user_table.dart b/lib/data/tables/user_table.dart index 59916114..ebadf67d 100644 --- a/lib/data/tables/user_table.dart +++ b/lib/data/tables/user_table.dart @@ -2,6 +2,7 @@ import 'package:drift/drift.dart'; import 'package:uuid/uuid.dart'; class Users extends Table { + TextColumn get storeIncarnation => text().nullable()(); TextColumn get id => text().clientDefault(() => Uuid().v7())(); IntColumn get spareTime => integer()(); TextColumn get note => text()(); diff --git a/lib/domain/entities/schedule_save.dart b/lib/domain/entities/schedule_save.dart new file mode 100644 index 00000000..38959f90 --- /dev/null +++ b/lib/domain/entities/schedule_save.dart @@ -0,0 +1,78 @@ +import 'package:on_time_front/domain/recurrence/recurring_schedule.dart'; +import 'package:equatable/equatable.dart'; +import 'package:on_time_front/domain/entities/schedule_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_entity.dart'; + +class ScheduleEditBaseline extends Equatable { + const ScheduleEditBaseline({ + required this.store, + required this.generation, + required this.revision, + this.incarnation, + this.version, + this.rootId, + this.rootIncarnation, + this.rootVersion, + }); + final String store; + final int generation; + final int revision; + final String? incarnation; + final int? version; + final String? rootId; + final String? rootIncarnation; + final int? rootVersion; + @override + List get props => [ + store, + generation, + revision, + incarnation, + version, + rootId, + rootIncarnation, + rootVersion, + ]; +} + +class ScheduleEditSnapshot { + const ScheduleEditSnapshot( + this.schedule, + this.preparation, + this.baseline, { + this.segment, + }); + final RecurringSegment? segment; + final ScheduleEntity schedule; + final PreparationEntity preparation; + final ScheduleEditBaseline baseline; +} + +enum ScheduleSaveFailure { conflict, invalid, protected, unavailable } + +class ScheduleSaveRejected implements Exception { + const ScheduleSaveRejected(this.failure); + final ScheduleSaveFailure failure; +} + +class ScheduleSaveReceipt { + const ScheduleSaveReceipt({ + required this.scheduleId, + required this.mutationId, + required this.generation, + required this.changed, + this.deliveryPending = false, + }); + final String scheduleId; + final String mutationId; + final int generation; + final bool changed; + final bool deliveryPending; + ScheduleSaveReceipt withDeliveryPending(bool value) => ScheduleSaveReceipt( + scheduleId: scheduleId, + mutationId: mutationId, + generation: generation, + changed: changed, + deliveryPending: value, + ); +} diff --git a/lib/domain/repositories/schedule_aggregate_repository.dart b/lib/domain/repositories/schedule_aggregate_repository.dart new file mode 100644 index 00000000..32f4b122 --- /dev/null +++ b/lib/domain/repositories/schedule_aggregate_repository.dart @@ -0,0 +1,15 @@ +import 'package:on_time_front/domain/entities/preparation_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; +import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; + +abstract interface class ScheduleAggregateRepository { + Future newBaseline(); + Future<({ScheduleEditBaseline baseline, PreparationEntity preparation})> + newDraft(); + Future readForEdit(String id); + Future save( + ScheduleFormSubmission submission, { + required bool editing, + }); + bool isCurrent(ScheduleSaveReceipt receipt); +} diff --git a/lib/domain/use-cases/create_schedule_form_submission_use_case.dart b/lib/domain/use-cases/create_schedule_form_submission_use_case.dart index 3ce0c7ed..f6a076e1 100644 --- a/lib/domain/use-cases/create_schedule_form_submission_use_case.dart +++ b/lib/domain/use-cases/create_schedule_form_submission_use_case.dart @@ -1,33 +1,14 @@ -import 'package:on_time_front/domain/use-cases/recurring_schedules_use_case.dart'; import 'package:injectable/injectable.dart'; -import 'package:on_time_front/domain/use-cases/create_custom_preparation_use_case.dart'; -import 'package:on_time_front/domain/use-cases/create_schedule_with_place_use_case.dart'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; +import 'package:on_time_front/domain/use-cases/schedule_save_workflow.dart'; @Injectable() class CreateScheduleFormSubmissionUseCase { - final CreateScheduleWithPlaceUseCase _createScheduleWithPlaceUseCase; - final CreateCustomPreparationUseCase _createCustomPreparationUseCase; - - CreateScheduleFormSubmissionUseCase( - this._createScheduleWithPlaceUseCase, - this._createCustomPreparationUseCase, { - RecurringSchedulesUseCase? recurringSchedules, - }) : _recurring = recurringSchedules; - - final RecurringSchedulesUseCase? _recurring; - - Future call(ScheduleFormSubmission submission) async { - if (submission.recurrenceRule != null) { - await _recurring!.save(submission); - return; - } - await _createScheduleWithPlaceUseCase(submission.schedule); - if (submission.preparationChanged) { - await _createCustomPreparationUseCase( - submission.preparation, - submission.schedule.id, - ); - } - } + CreateScheduleFormSubmissionUseCase(this.workflow); + final ScheduleSaveWorkflow workflow; + Future call(ScheduleFormSubmission submission) => + workflow.save(submission, editing: false); + Future retryDelivery(ScheduleSaveReceipt receipt) => + workflow.retryDelivery(receipt); } diff --git a/lib/domain/use-cases/load_schedule_form_draft_use_case.dart b/lib/domain/use-cases/load_schedule_form_draft_use_case.dart index df158e02..815d2533 100644 --- a/lib/domain/use-cases/load_schedule_form_draft_use_case.dart +++ b/lib/domain/use-cases/load_schedule_form_draft_use_case.dart @@ -1,3 +1,6 @@ +import 'package:on_time_front/domain/recurrence/recurring_schedule.dart'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; +import 'package:on_time_front/domain/repositories/schedule_aggregate_repository.dart'; import 'package:on_time_front/domain/entities/schedule_entity.dart'; import 'package:equatable/equatable.dart'; import 'package:injectable/injectable.dart'; @@ -11,6 +14,8 @@ import 'package:on_time_front/domain/use-cases/load_preparation_by_schedule_id_u import 'package:uuid/uuid.dart'; class ScheduleFormDraft extends Equatable { + final RecurringSegment? segment; + final ScheduleEditBaseline? baseline; final String id; final String? placeId; final String? placeName; @@ -27,6 +32,8 @@ class ScheduleFormDraft extends Equatable { final ScheduleEntity? originalSchedule; const ScheduleFormDraft({ + this.segment, + this.baseline, required this.id, required this.placeId, required this.placeName, @@ -45,6 +52,7 @@ class ScheduleFormDraft extends Equatable { @override List get props => [ + baseline, id, placeId, placeName, @@ -68,6 +76,7 @@ class LoadScheduleFormDraftUseCase { final GetPreparationByScheduleIdUseCase _getPreparationByScheduleIdUseCase; final GetDefaultPreparationUseCase _getDefaultPreparationUseCase; final GetScheduleByIdUseCase _getScheduleByIdUseCase; + final ScheduleAggregateRepository? _aggregate; final DateTime Function() _now; final String Function() _newId; final Future Function() _timeZoneId; @@ -77,7 +86,9 @@ class LoadScheduleFormDraftUseCase { this._getPreparationByScheduleIdUseCase, this._getDefaultPreparationUseCase, this._getScheduleByIdUseCase, - ) : _now = DateTime.now, + ScheduleAggregateRepository aggregate, + ) : _aggregate = aggregate, + _now = DateTime.now, _newId = const Uuid().v7, _timeZoneId = LocalTimeZoneService.current; @@ -89,7 +100,9 @@ class LoadScheduleFormDraftUseCase { required DateTime Function() now, required String Function() newId, Future Function()? timeZoneId, - }) : _now = now, + ScheduleAggregateRepository? aggregate, + }) : _aggregate = aggregate, + _now = now, _newId = newId, _timeZoneId = timeZoneId ?? LocalTimeZoneService.current; @@ -97,9 +110,12 @@ class LoadScheduleFormDraftUseCase { DateTime? initialDate, Duration? currentUserSpareTime, }) async { - final defaultPreparation = await _getDefaultPreparationUseCase(); + final snapshot = await _aggregate?.newDraft(); + final defaultPreparation = + snapshot?.preparation ?? await _getDefaultPreparationUseCase(); return ScheduleFormDraft( + baseline: snapshot?.baseline, id: _newId(), placeId: _newId(), placeName: null, @@ -119,11 +135,17 @@ class LoadScheduleFormDraftUseCase { } Future edit(String scheduleId) async { - await _loadPreparationByScheduleIdUseCase(scheduleId); - final preparation = await _getPreparationByScheduleIdUseCase(scheduleId); - final schedule = await _getScheduleByIdUseCase(scheduleId); + final snapshot = await _aggregate?.readForEdit(scheduleId); + if (snapshot == null) await _loadPreparationByScheduleIdUseCase(scheduleId); + final preparation = + snapshot?.preparation ?? + await _getPreparationByScheduleIdUseCase(scheduleId); + final schedule = + snapshot?.schedule ?? await _getScheduleByIdUseCase(scheduleId); return ScheduleFormDraft( + segment: snapshot?.segment, + baseline: snapshot?.baseline, id: schedule.id, placeId: schedule.place.id, placeName: schedule.place.placeName, diff --git a/lib/domain/use-cases/recurring_schedules_use_case.dart b/lib/domain/use-cases/recurring_schedules_use_case.dart index 989d05ae..de23e056 100644 --- a/lib/domain/use-cases/recurring_schedules_use_case.dart +++ b/lib/domain/use-cases/recurring_schedules_use_case.dart @@ -78,47 +78,6 @@ class RecurringSchedulesUseCase { return _recurring.review(value.schedule, value.preparation, rule); } - Future save(ScheduleFormSubmission value) async { - final original = value.originalSchedule; - if (original != null && - (original.isRecurring || original.preparationDefinitionId != null)) { - if (original.isRecurring && - value.recurringScope == RecurringEditScope.following) { - await _recurring.updateFollowing( - original, - value.schedule, - value.preparation, - value.recurrenceRule!, - countChanged: value.recurrenceCountChanged, - excludedSlots: value.excludedSlots, - reviewedFirstSlotKey: value.reviewedFirstSlotKey, - confirmDetached: value.confirmDetached, - ); - } else { - await _recurring.updateOccurrence( - original, - value.schedule, - value.preparation, - preparationChanged: value.preparationChanged, - ); - } - } else { - await _recurring.create( - value.schedule, - value.preparation, - value.recurrenceRule!, - excludedSlots: value.excludedSlots, - reviewedFirstSlotKey: value.reviewedFirstSlotKey, - ); - } - await _alarms( - operation: original == null - ? ScheduleMutationAlarmOperation.created - : ScheduleMutationAlarmOperation.updated, - scheduleId: value.schedule.id, - ); - } - Future delete( ScheduleEntity occurrence, RecurringEditScope scope, diff --git a/lib/domain/use-cases/schedule_form_submission.dart b/lib/domain/use-cases/schedule_form_submission.dart index 0782f3c2..c2f784c5 100644 --- a/lib/domain/use-cases/schedule_form_submission.dart +++ b/lib/domain/use-cases/schedule_form_submission.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'package:on_time_front/domain/recurrence/recurrence_rule.dart'; import 'package:on_time_front/domain/recurrence/recurring_schedule.dart'; import 'package:equatable/equatable.dart'; @@ -6,6 +7,8 @@ import 'package:on_time_front/domain/entities/schedule_entity.dart'; import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart'; class ScheduleFormSubmission extends Equatable { + final String? mutationId; + final ScheduleEditBaseline? baseline; final ScheduleEntity schedule; final PreparationEntity preparation; final bool preparationChanged; @@ -19,6 +22,8 @@ class ScheduleFormSubmission extends Equatable { final String? reviewedFirstSlotKey; const ScheduleFormSubmission({ + this.mutationId, + this.baseline, required this.schedule, required this.preparation, required this.preparationChanged, @@ -34,6 +39,8 @@ class ScheduleFormSubmission extends Equatable { @override List get props => [ + mutationId, + baseline, schedule, preparation, preparationChanged, diff --git a/lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart b/lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart index 642ef512..f22ffdc8 100644 --- a/lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart +++ b/lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/alarm_entities.dart'; import 'dart:async'; import 'package:injectable/injectable.dart'; @@ -19,6 +20,15 @@ class ScheduleMutationAlarmEffectsCoordinator { this._reconcileAlarmsUseCase, ); + /// Await the accepted current-data reconciliation after an aggregate commit. + /// A failed projection never changes the durable commit fact. + Future afterCommit() async { + final result = await _reconcileAlarmsUseCase(); + return result.failures.isEmpty && + (result.status == AlarmReconciliationStatus.armed || + result.status == AlarmReconciliationStatus.disabled); + } + Future call({ required ScheduleMutationAlarmOperation operation, required String scheduleId, diff --git a/lib/domain/use-cases/schedule_save_workflow.dart b/lib/domain/use-cases/schedule_save_workflow.dart new file mode 100644 index 00000000..0ff07ae8 --- /dev/null +++ b/lib/domain/use-cases/schedule_save_workflow.dart @@ -0,0 +1,74 @@ +import 'package:injectable/injectable.dart'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; +import 'package:on_time_front/domain/repositories/schedule_aggregate_repository.dart'; +import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; +import 'package:on_time_front/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart'; + +@Injectable() +class ScheduleSaveWorkflow { + ScheduleSaveWorkflow(this.repository, this.effects); + final ScheduleAggregateRepository repository; + final ScheduleMutationAlarmEffectsCoordinator effects; + final _pending = + <(bool, ScheduleFormSubmission), Future>{}; + final _delivery = Expando>(); + Future save( + ScheduleFormSubmission value, { + required bool editing, + }) { + final key = (editing, value); + final pending = _pending[key]; + if (pending != null) return pending; + final future = _save(value, editing: editing); + _pending[key] = future; + future.then( + (_) { + _pending.remove(key); + }, + onError: (Object _, StackTrace __) { + _pending.remove(key); + }, + ); + return future; + } + + Future _save( + ScheduleFormSubmission value, { + required bool editing, + }) async { + final receipt = await repository.save(value, editing: editing); + return retryDelivery(receipt); + } + + Future retryDelivery(ScheduleSaveReceipt receipt) { + final pending = _delivery[receipt]; + if (pending != null) return pending; + final future = _retryDelivery(receipt); + _delivery[receipt] = future; + future.then( + (_) { + _delivery[receipt] = null; + }, + onError: (Object _, StackTrace __) { + _delivery[receipt] = null; + }, + ); + return future; + } + + Future _retryDelivery( + ScheduleSaveReceipt receipt, + ) async { + if (!repository.isCurrent(receipt)) { + return receipt.withDeliveryPending(true); + } + try { + final complete = await effects.afterCommit(); + return receipt.withDeliveryPending( + !complete || !repository.isCurrent(receipt), + ); + } catch (_) { + return receipt.withDeliveryPending(true); + } + } +} diff --git a/lib/domain/use-cases/update_schedule_form_submission_use_case.dart b/lib/domain/use-cases/update_schedule_form_submission_use_case.dart index dce43e13..f895691d 100644 --- a/lib/domain/use-cases/update_schedule_form_submission_use_case.dart +++ b/lib/domain/use-cases/update_schedule_form_submission_use_case.dart @@ -1,67 +1,14 @@ -import 'package:on_time_front/domain/use-cases/recurring_schedules_use_case.dart'; import 'package:injectable/injectable.dart'; -import 'package:on_time_front/domain/entities/preparation_entity.dart'; -import 'package:on_time_front/domain/entities/preparation_step_entity.dart'; -import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; -import 'package:on_time_front/domain/use-cases/update_preparation_by_schedule_id_use_case.dart'; -import 'package:on_time_front/domain/use-cases/update_schedule_use_case.dart'; -import 'package:uuid/uuid.dart'; +import 'package:on_time_front/domain/use-cases/schedule_save_workflow.dart'; @Injectable() class UpdateScheduleFormSubmissionUseCase { - final UpdateScheduleUseCase _updateScheduleUseCase; - final UpdatePreparationByScheduleIdUseCase - _updatePreparationByScheduleIdUseCase; - final String Function() _newId; - final RecurringSchedulesUseCase? _recurring; - - UpdateScheduleFormSubmissionUseCase( - this._updateScheduleUseCase, - this._updatePreparationByScheduleIdUseCase, { - RecurringSchedulesUseCase? recurringSchedules, - }) : _recurring = recurringSchedules, - _newId = const Uuid().v7; - - UpdateScheduleFormSubmissionUseCase.withIdGenerator( - this._updateScheduleUseCase, - this._updatePreparationByScheduleIdUseCase, { - required String Function() newId, - }) : _recurring = null, - _newId = newId; - - Future call(ScheduleFormSubmission submission) async { - if (submission.originalSchedule?.preparationDefinitionId != null) { - await _recurring!.save(submission); - return; - } - await _updateScheduleUseCase(submission.schedule); - if (submission.preparationChanged) { - await _updatePreparationByScheduleIdUseCase( - _preparationForUpdate(submission), - submission.schedule.id, - ); - } - } - - PreparationEntity _preparationForUpdate(ScheduleFormSubmission submission) { - if (submission.originalPreparationMode != - SchedulePreparationMode.defaultPreparation) { - return submission.preparation; - } - - final orderedSteps = submission.preparation.ordered.preparationStepList; - final newIds = List.generate(orderedSteps.length, (_) => _newId()); - final copiedSteps = [ - for (var i = 0; i < orderedSteps.length; i++) - PreparationStepEntity( - id: newIds[i], - preparationName: orderedSteps[i].preparationName, - preparationTime: orderedSteps[i].preparationTime, - nextPreparationId: i + 1 < newIds.length ? newIds[i + 1] : null, - ), - ]; - - return PreparationEntity(preparationStepList: copiedSteps); - } + UpdateScheduleFormSubmissionUseCase(this.workflow); + final ScheduleSaveWorkflow workflow; + Future call(ScheduleFormSubmission submission) => + workflow.save(submission, editing: true); + Future retryDelivery(ScheduleSaveReceipt receipt) => + workflow.retryDelivery(receipt); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 41a0081e..c19f52ec 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -688,5 +688,14 @@ } } }, - "dataResetFailed": "Local data could not be reset. Try again." + "dataResetFailed": "Local data could not be reset. Try again.", + "scheduleSavedPending": "Schedule saved", + "scheduleDeliveryPendingBody": "Notifications could not be updated. Retry updates notifications without saving the schedule again.", + "scheduleSaveConflict": "Review changes before saving", + "scheduleSaveConflictBody": "Local data changed after this form was opened. Your draft is preserved. A new schedule also requires review when other data changes.", + "scheduleReviewCurrent": "Review current data", + "scheduleCreateReview": "Prepare a new save using the current local data. Review your draft, then press Save again.", + "scheduleDraftPreserved": "This is the currently saved schedule. Confirm to preserve your draft and prepare a new save. Review the changes, then press Save again.", + "scheduleSaveProtected": "Schedules that have started or reached preparation time cannot be edited. Your draft is preserved.", + "scheduleSaveUnavailable": "Unable to save. Your draft is preserved. Check the current state and preparation steps." } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 48c2a769..173caa8e 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -285,5 +285,14 @@ } } }, - "dataResetFailed": "초기화하지 못했습니다. 다시 시도해 주세요." + "dataResetFailed": "초기화하지 못했습니다. 다시 시도해 주세요.", + "scheduleSavedPending": "일정은 저장됐어요", + "scheduleDeliveryPendingBody": "알림 업데이트를 완료하지 못했어요. 다시 시도하면 저장한 일정은 그대로 두고 알림만 업데이트합니다.", + "scheduleSaveConflict": "저장 전에 변경 사항을 확인해 주세요", + "scheduleSaveConflictBody": "화면을 연 뒤 로컬 데이터가 바뀌었어요. 입력은 유지됩니다. 새 일정은 다른 데이터가 바뀌어도 다시 확인해야 합니다.", + "scheduleReviewCurrent": "현재 데이터 확인", + "scheduleCreateReview": "현재 로컬 데이터를 기준으로 새 저장을 준비합니다. 입력 내용을 확인한 뒤 저장 버튼을 다시 눌러 주세요.", + "scheduleDraftPreserved": "위 내용은 현재 저장된 일정입니다. 확인하면 입력한 초안을 유지하며 새 저장을 준비합니다. 변경 내용을 검토한 뒤 저장을 다시 눌러 주세요.", + "scheduleSaveProtected": "이미 시작됐거나 준비 시간이 지난 일정은 수정할 수 없어요. 입력 내용은 유지됩니다.", + "scheduleSaveUnavailable": "저장할 수 없어요. 입력을 유지했으니 현재 상태와 준비 단계를 확인해 주세요." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3039f29d..44e55696 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1322,6 +1322,60 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Local data could not be reset. Try again.'** String get dataResetFailed; + + /// No description provided for @scheduleSavedPending. + /// + /// In en, this message translates to: + /// **'Schedule saved'** + String get scheduleSavedPending; + + /// No description provided for @scheduleDeliveryPendingBody. + /// + /// In en, this message translates to: + /// **'Notifications could not be updated. Retry updates notifications without saving the schedule again.'** + String get scheduleDeliveryPendingBody; + + /// No description provided for @scheduleSaveConflict. + /// + /// In en, this message translates to: + /// **'Review changes before saving'** + String get scheduleSaveConflict; + + /// No description provided for @scheduleSaveConflictBody. + /// + /// In en, this message translates to: + /// **'Local data changed after this form was opened. Your draft is preserved. A new schedule also requires review when other data changes.'** + String get scheduleSaveConflictBody; + + /// No description provided for @scheduleReviewCurrent. + /// + /// In en, this message translates to: + /// **'Review current data'** + String get scheduleReviewCurrent; + + /// No description provided for @scheduleCreateReview. + /// + /// In en, this message translates to: + /// **'Prepare a new save using the current local data. Review your draft, then press Save again.'** + String get scheduleCreateReview; + + /// No description provided for @scheduleDraftPreserved. + /// + /// In en, this message translates to: + /// **'This is the currently saved schedule. Confirm to preserve your draft and prepare a new save. Review the changes, then press Save again.'** + String get scheduleDraftPreserved; + + /// No description provided for @scheduleSaveProtected. + /// + /// In en, this message translates to: + /// **'Schedules that have started or reached preparation time cannot be edited. Your draft is preserved.'** + String get scheduleSaveProtected; + + /// No description provided for @scheduleSaveUnavailable. + /// + /// In en, this message translates to: + /// **'Unable to save. Your draft is preserved. Check the current state and preparation steps.'** + String get scheduleSaveUnavailable; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 760fe79e..7110322b 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -721,4 +721,37 @@ class AppLocalizationsEn extends AppLocalizations { @override String get dataResetFailed => 'Local data could not be reset. Try again.'; + + @override + String get scheduleSavedPending => 'Schedule saved'; + + @override + String get scheduleDeliveryPendingBody => + 'Notifications could not be updated. Retry updates notifications without saving the schedule again.'; + + @override + String get scheduleSaveConflict => 'Review changes before saving'; + + @override + String get scheduleSaveConflictBody => + 'Local data changed after this form was opened. Your draft is preserved. A new schedule also requires review when other data changes.'; + + @override + String get scheduleReviewCurrent => 'Review current data'; + + @override + String get scheduleCreateReview => + 'Prepare a new save using the current local data. Review your draft, then press Save again.'; + + @override + String get scheduleDraftPreserved => + 'This is the currently saved schedule. Confirm to preserve your draft and prepare a new save. Review the changes, then press Save again.'; + + @override + String get scheduleSaveProtected => + 'Schedules that have started or reached preparation time cannot be edited. Your draft is preserved.'; + + @override + String get scheduleSaveUnavailable => + 'Unable to save. Your draft is preserved. Check the current state and preparation steps.'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 653c038c..1fa4bd5f 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -668,4 +668,37 @@ class AppLocalizationsKo extends AppLocalizations { @override String get dataResetFailed => '초기화하지 못했습니다. 다시 시도해 주세요.'; + + @override + String get scheduleSavedPending => '일정은 저장됐어요'; + + @override + String get scheduleDeliveryPendingBody => + '알림 업데이트를 완료하지 못했어요. 다시 시도하면 저장한 일정은 그대로 두고 알림만 업데이트합니다.'; + + @override + String get scheduleSaveConflict => '저장 전에 변경 사항을 확인해 주세요'; + + @override + String get scheduleSaveConflictBody => + '화면을 연 뒤 로컬 데이터가 바뀌었어요. 입력은 유지됩니다. 새 일정은 다른 데이터가 바뀌어도 다시 확인해야 합니다.'; + + @override + String get scheduleReviewCurrent => '현재 데이터 확인'; + + @override + String get scheduleCreateReview => + '현재 로컬 데이터를 기준으로 새 저장을 준비합니다. 입력 내용을 확인한 뒤 저장 버튼을 다시 눌러 주세요.'; + + @override + String get scheduleDraftPreserved => + '위 내용은 현재 저장된 일정입니다. 확인하면 입력한 초안을 유지하며 새 저장을 준비합니다. 변경 내용을 검토한 뒤 저장을 다시 눌러 주세요.'; + + @override + String get scheduleSaveProtected => + '이미 시작됐거나 준비 시간이 지난 일정은 수정할 수 없어요. 입력 내용은 유지됩니다.'; + + @override + String get scheduleSaveUnavailable => + '저장할 수 없어요. 입력을 유지했으니 현재 상태와 준비 단계를 확인해 주세요.'; } diff --git a/lib/presentation/schedule_create/bloc/schedule_form_bloc.dart b/lib/presentation/schedule_create/bloc/schedule_form_bloc.dart index f43b7e14..8126fd2d 100644 --- a/lib/presentation/schedule_create/bloc/schedule_form_bloc.dart +++ b/lib/presentation/schedule_create/bloc/schedule_form_bloc.dart @@ -1,3 +1,5 @@ +import 'package:on_time_front/domain/entities/schedule_save.dart'; +import 'package:on_time_front/domain/repositories/schedule_aggregate_repository.dart'; import 'package:on_time_front/domain/recurrence/recurrence_rule.dart'; import 'package:on_time_front/domain/recurrence/recurrence_engine.dart'; import 'package:on_time_front/domain/recurrence/recurring_schedule.dart'; @@ -25,8 +27,22 @@ class ScheduleFormBloc extends Bloc { this._createScheduleFormSubmissionUseCase, this._updateScheduleFormSubmissionUseCase, { RecurringSchedulesUseCase? recurringSchedules, - }) : _recurring = recurringSchedules, + ScheduleAggregateRepository? aggregate, + }) : _aggregate = aggregate, + _recurring = recurringSchedules, super(ScheduleFormState()) { + on((event, emit) { + if (!_owns(event.owner) || _saving) return; + emit( + state.copyWith( + baseline: event.baseline, + mutationId: const Uuid().v7(), + originalSchedule: event.current, + saveFailure: null, + submissionStatus: ScheduleFormSubmissionStatus.idle, + ), + ); + }); on(_onEditRequested); on(_onCreateRequested); on(_onScheduleNameChanged); @@ -39,22 +55,29 @@ class ScheduleFormBloc extends Bloc { on(_onCreated); on(_onValidated); on( - (event, emit) => emit( - state.copyWith( - recurrenceRule: event.rule, - recurrenceCountChanged: - state.recurrenceCountChanged || event.countChanged, - submissionStatus: ScheduleFormSubmissionStatus.idle, - recurrenceReview: null, - ), - ), + (event, emit) => _saving + ? null + : emit( + state.copyWith( + recurrenceRule: event.rule, + recurrenceCountChanged: + state.recurrenceCountChanged || event.countChanged, + submissionStatus: ScheduleFormSubmissionStatus.idle, + recurrenceReview: null, + ), + ), ); on( - (event, emit) => emit( - state.copyWith(submissionStatus: ScheduleFormSubmissionStatus.idle), - ), + (event, emit) => _saving + ? null + : emit( + state.copyWith( + submissionStatus: ScheduleFormSubmissionStatus.idle, + ), + ), ); on((event, emit) async { + if (_saving) return; final rule = state.recurrenceRule!; emit( state.copyWith( @@ -75,6 +98,42 @@ class ScheduleFormBloc extends Bloc { } final RecurringSchedulesUseCase? _recurring; + final ScheduleAggregateRepository? _aggregate; + Object _formOwner = Object(); + final _pendingOwners = {}; + Object get formOwner => _formOwner; + bool ownsForm(Object owner) => _owns(owner); + bool _owns(Object owner) => !isClosed && identical(owner, _formOwner); + bool get _saving => _pendingOwners.contains(_formOwner); + + Future< + ({ + Object owner, + ScheduleEditBaseline baseline, + ScheduleEditSnapshot? current, + }) + > + reviewCurrent() async { + final owner = _formOwner; + final current = state.originalSchedule == null + ? null + : await _aggregate!.readForEdit(state.id); + final baseline = current?.baseline ?? await _aggregate!.newBaseline(); + if (!_owns(owner)) { + throw const ScheduleSaveRejected(ScheduleSaveFailure.unavailable); + } + return (owner: owner, baseline: baseline, current: current); + } + + void acceptReviewedBaseline( + Object owner, + ScheduleEditBaseline baseline, + ScheduleEntity? current, + ) { + if (!_owns(owner) || _saving) return; + add(ScheduleFormBaselineAccepted(owner, baseline, current)); + } + final LoadScheduleFormDraftUseCase _loadScheduleFormDraftUseCase; final CreateScheduleFormSubmissionUseCase _createScheduleFormSubmissionUseCase; @@ -85,6 +144,7 @@ class ScheduleFormBloc extends Bloc { ScheduleFormEditRequested event, Emitter emit, ) async { + final owner = _formOwner = Object(); emit( state.copyWith( status: ScheduleFormStatus.loading, @@ -94,17 +154,19 @@ class ScheduleFormBloc extends Bloc { ); final draft = await _loadScheduleFormDraftUseCase.edit(event.scheduleId); + if (!_owns(owner)) return; final original = draft.originalSchedule; final following = (original?.isRecurring ?? false) && event.scope == RecurringEditScope.following; _emitLoadedDraft(draft, emit, ready: !following); if (following) { - final segment = await _recurring!.getSegment( - original!.recurringSegmentId!, - ); + final segment = + draft.segment ?? + await _recurring!.getSegment(original!.recurringSegmentId!); + if (!_owns(owner)) return; if (event.scope == RecurringEditScope.following) { - final day = DateTime.parse(original.recurringSlotKey!); + final day = DateTime.parse(original!.recurringSlotKey!); final start = DateTime( day.year, day.month, @@ -140,6 +202,7 @@ class ScheduleFormBloc extends Bloc { ScheduleFormCreateRequested event, Emitter emit, ) async { + final owner = _formOwner = Object(); emit( state.copyWith( status: ScheduleFormStatus.loading, @@ -152,6 +215,7 @@ class ScheduleFormBloc extends Bloc { initialDate: event.initialDate, currentUserSpareTime: event.currentUserSpareTime, ); + if (!_owns(owner)) return; _emitLoadedDraft(draft, emit); } @@ -159,6 +223,7 @@ class ScheduleFormBloc extends Bloc { ScheduleFormScheduleNameChanged event, Emitter emit, ) { + if (_saving) return; emit(state.copyWith(scheduleName: event.scheduleName)); } @@ -166,6 +231,7 @@ class ScheduleFormBloc extends Bloc { ScheduleFormScheduleDateTimeChanged event, Emitter emit, ) { + if (_saving) return; emit( state.copyWith( scheduleTime: DateTime( @@ -186,6 +252,7 @@ class ScheduleFormBloc extends Bloc { ScheduleFormPlaceNameChanged event, Emitter emit, ) { + if (_saving) return; emit(state.copyWith(placeName: event.placeName)); } @@ -193,6 +260,7 @@ class ScheduleFormBloc extends Bloc { ScheduleFormMoveTimeChanged event, Emitter emit, ) { + if (_saving) return; emit(state.copyWith(moveTime: event.moveTime)); } @@ -200,6 +268,7 @@ class ScheduleFormBloc extends Bloc { ScheduleFormScheduleSpareTimeChanged event, Emitter emit, ) { + if (_saving) return; emit(state.copyWith(scheduleSpareTime: event.scheduleSpareTime)); } @@ -207,6 +276,7 @@ class ScheduleFormBloc extends Bloc { ScheduleFormPreparationChanged event, Emitter emit, ) { + if (_saving) return; final IsPreparationChanged isChagned; if (state.preparation == event.preparation) { // not changed @@ -243,9 +313,12 @@ class ScheduleFormBloc extends Bloc { required bool confirmed, required Set excludedSlots, }) async { - if (state.submissionStatus == ScheduleFormSubmissionStatus.submitting) { + if (_saving || + state.submissionStatus == ScheduleFormSubmissionStatus.submitting) { return; } + final owner = _formOwner; + _pendingOwners.add(owner); emit( state.copyWith( submissionStatus: ScheduleFormSubmissionStatus.submitting, @@ -260,6 +333,7 @@ class ScheduleFormBloc extends Bloc { ); if (value.recurrenceRule != null && !confirmed) { final review = await _recurring!.review(value); + if (!_owns(owner)) return; if (review != null) { emit( state.copyWith( @@ -270,18 +344,36 @@ class ScheduleFormBloc extends Bloc { return; } } - if (editing) { - await _updateScheduleFormSubmissionUseCase(value); - } else { - await _createScheduleFormSubmissionUseCase(value); + final receipt = state.saveReceipt?.deliveryPending == true + ? await _createScheduleFormSubmissionUseCase.retryDelivery( + state.saveReceipt!, + ) + : editing + ? await _updateScheduleFormSubmissionUseCase(value) + : await _createScheduleFormSubmissionUseCase(value); + if (!_owns(owner) || + (_aggregate != null && !_aggregate.isCurrent(receipt))) { + return; } emit( state.copyWith( - submissionStatus: ScheduleFormSubmissionStatus.success, + submissionStatus: receipt.deliveryPending + ? ScheduleFormSubmissionStatus.deliveryPending + : ScheduleFormSubmissionStatus.success, + saveReceipt: receipt, submissionError: null, ), ); + } on ScheduleSaveRejected catch (e) { + if (!_owns(owner)) return; + emit( + state.copyWith( + submissionStatus: ScheduleFormSubmissionStatus.failure, + saveFailure: e.failure, + ), + ); } on RepeatedTimeChoiceRequired catch (e) { + if (!_owns(owner)) return; emit( state.copyWith( submissionStatus: ScheduleFormSubmissionStatus.timeChoice, @@ -289,6 +381,7 @@ class ScheduleFormBloc extends Bloc { ), ); } on RecurrenceNeedsReview catch (e) { + if (!_owns(owner)) return; emit( state.copyWith( submissionStatus: ScheduleFormSubmissionStatus.review, @@ -296,9 +389,16 @@ class ScheduleFormBloc extends Bloc { ), ); } catch (e) { + if (!_owns(owner)) return; emit( state.copyWith( submissionStatus: ScheduleFormSubmissionStatus.failure, + saveFailure: + e is RecurrenceValidationException || + e is RecurrenceSearchLimit || + e is ArgumentError + ? null + : ScheduleSaveFailure.unavailable, submissionError: e is RecurrenceValidationException ? e.message : e is RecurrenceSearchLimit @@ -308,6 +408,8 @@ class ScheduleFormBloc extends Bloc { : '저장하지 못했어요. 입력 내용은 유지되며 다시 시도할 수 있어요.', ), ); + } finally { + _pendingOwners.remove(owner); } } @@ -315,6 +417,7 @@ class ScheduleFormBloc extends Bloc { ScheduleFormValidated event, Emitter emit, ) { + if (_saving) return; emit(state.copyWith(isValid: event.isValid)); } @@ -324,10 +427,12 @@ class ScheduleFormBloc extends Bloc { bool ready = true, }) { emit( - state.copyWith( + ScheduleFormState( status: ready ? ScheduleFormStatus.success : ScheduleFormStatus.loading, submissionStatus: ScheduleFormSubmissionStatus.idle, submissionError: null, + baseline: draft.baseline, + mutationId: const Uuid().v7(), id: draft.id, placeId: draft.placeId, placeName: draft.placeName, @@ -357,6 +462,8 @@ class ScheduleFormBloc extends Bloc { return ScheduleFormSubmission( schedule: scheduleEntity, preparation: state.preparation!, + baseline: state.baseline, + mutationId: state.mutationId, preparationChanged: state.originalSchedule?.preparationDefinitionId != null ? state.preparation != state.originalPreparation diff --git a/lib/presentation/schedule_create/bloc/schedule_form_event.dart b/lib/presentation/schedule_create/bloc/schedule_form_event.dart index 6b78b49d..108ef11d 100644 --- a/lib/presentation/schedule_create/bloc/schedule_form_event.dart +++ b/lib/presentation/schedule_create/bloc/schedule_form_event.dart @@ -155,3 +155,12 @@ final class ScheduleFormRepeatedTimeChosen extends ScheduleFormEvent { @override List get props => [choice]; } + +final class ScheduleFormBaselineAccepted extends ScheduleFormEvent { + const ScheduleFormBaselineAccepted(this.owner, this.baseline, this.current); + final Object owner; + final ScheduleEditBaseline baseline; + final ScheduleEntity? current; + @override + List get props => [owner, baseline, ?current]; +} diff --git a/lib/presentation/schedule_create/bloc/schedule_form_state.dart b/lib/presentation/schedule_create/bloc/schedule_form_state.dart index c627bcc2..8d9f12df 100644 --- a/lib/presentation/schedule_create/bloc/schedule_form_state.dart +++ b/lib/presentation/schedule_create/bloc/schedule_form_state.dart @@ -9,6 +9,7 @@ enum ScheduleFormSubmissionStatus { failure, review, timeChoice, + deliveryPending, } enum IsPreparationChanged { changed, unchanged } @@ -16,6 +17,10 @@ enum IsPreparationChanged { changed, unchanged } final class ScheduleFormState extends Equatable { static const _unset = Object(); + final ScheduleEditBaseline? baseline; + final String mutationId; + final ScheduleSaveReceipt? saveReceipt; + final ScheduleSaveFailure? saveFailure; final ScheduleFormStatus status; final ScheduleFormSubmissionStatus submissionStatus; final String? submissionError; @@ -44,6 +49,10 @@ final class ScheduleFormState extends Equatable { final String? previousScheduleName; ScheduleFormState({ + this.baseline, + String? mutationId, + this.saveReceipt, + this.saveFailure, this.status = ScheduleFormStatus.initial, this.submissionStatus = ScheduleFormSubmissionStatus.idle, this.submissionError, @@ -70,9 +79,14 @@ final class ScheduleFormState extends Equatable { this.isValid = false, this.maxAvailableTime, this.previousScheduleName, - }) : id = id ?? Uuid().v7(); + }) : mutationId = mutationId ?? const Uuid().v7(), + id = id ?? Uuid().v7(); ScheduleFormState copyWith({ + ScheduleEditBaseline? baseline, + String? mutationId, + ScheduleSaveReceipt? saveReceipt, + Object? saveFailure = _unset, ScheduleFormStatus? status, ScheduleFormSubmissionStatus? submissionStatus, Object? submissionError = _unset, @@ -101,6 +115,12 @@ final class ScheduleFormState extends Equatable { Object? previousScheduleName = _unset, }) { return ScheduleFormState( + baseline: baseline ?? this.baseline, + mutationId: mutationId ?? this.mutationId, + saveReceipt: saveReceipt ?? this.saveReceipt, + saveFailure: identical(saveFailure, _unset) + ? this.saveFailure + : saveFailure as ScheduleSaveFailure?, status: status ?? this.status, submissionStatus: submissionStatus ?? this.submissionStatus, submissionError: identical(submissionError, _unset) @@ -164,6 +184,16 @@ final class ScheduleFormState extends Equatable { scheduleSpareTime: state.scheduleSpareTime, scheduleNote: state.scheduleNote ?? '', isStarted: false, + preparationMode: state.isChanged == IsPreparationChanged.changed + ? SchedulePreparationMode.custom + : (originalSchedule?.preparationMode ?? + SchedulePreparationMode.defaultPreparation), + preparationTemplateId: state.isChanged == IsPreparationChanged.changed + ? null + : originalSchedule?.preparationTemplateId, + preparationTemplateName: state.isChanged == IsPreparationChanged.changed + ? null + : originalSchedule?.preparationTemplateName, recurringSegmentId: originalSchedule?.recurringSegmentId, recurringSlotKey: originalSchedule?.recurringSlotKey, recurringOrdinal: originalSchedule?.recurringOrdinal, @@ -174,6 +204,10 @@ final class ScheduleFormState extends Equatable { @override List get props => [ + baseline, + mutationId, + saveReceipt, + saveFailure, status, submissionStatus, submissionError, diff --git a/lib/presentation/schedule_create/components/schedule_multi_page_form.dart b/lib/presentation/schedule_create/components/schedule_multi_page_form.dart index 3db0f405..79e6e92a 100644 --- a/lib/presentation/schedule_create/components/schedule_multi_page_form.dart +++ b/lib/presentation/schedule_create/components/schedule_multi_page_form.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'package:on_time_front/presentation/recurring/recurrence_time_choice_sheet.dart'; import 'package:on_time_front/presentation/recurring/recurrence_components.dart'; import 'package:on_time_front/domain/recurrence/recurrence_rule.dart'; @@ -64,8 +65,17 @@ class _ScheduleMultiPageFormState extends State listenWhen: (previous, current) => previous.submissionStatus != current.submissionStatus, listener: (context, state) async { + final bloc = context.read(); + final owner = bloc.formOwner; + final mutation = state.mutationId; + bool ownsCurrentForm() => + mounted && + context.mounted && + bloc.ownsForm(owner) && + bloc.state.mutationId == mutation && + (ModalRoute.of(context)?.isCurrent ?? false); + if (!context.mounted || !ownsCurrentForm()) return; if (state.submissionStatus == ScheduleFormSubmissionStatus.review) { - final bloc = context.read(); final selected = await showModalBottomSheet>( context: context, isScrollControlled: true, @@ -78,7 +88,7 @@ class _ScheduleMultiPageFormState extends State ), ), ); - if (bloc.isClosed) return; + if (!context.mounted || !ownsCurrentForm()) return; if (selected == null) { bloc.add(const ScheduleFormReviewDismissed()); } else if (state.originalSchedule == null) { @@ -92,7 +102,6 @@ class _ScheduleMultiPageFormState extends State } } else if (state.submissionStatus == ScheduleFormSubmissionStatus.timeChoice) { - final bloc = context.read(); final choice = await showModalBottomSheet( context: context, isScrollControlled: true, @@ -105,7 +114,7 @@ class _ScheduleMultiPageFormState extends State ), ), ); - if (bloc.isClosed) return; + if (!context.mounted || !ownsCurrentForm()) return; if (choice == null) { bloc.add(const ScheduleFormReviewDismissed()); } else { @@ -114,8 +123,101 @@ class _ScheduleMultiPageFormState extends State } else if (state.submissionStatus == ScheduleFormSubmissionStatus.success) { Navigator.of(context).pop(true); + } else if (state.submissionStatus == + ScheduleFormSubmissionStatus.deliveryPending) { + final l = AppLocalizations.of(context)!; + final retry = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l.scheduleSavedPending), + content: Text(l.scheduleDeliveryPendingBody), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(l.ok), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(l.dataRetry), + ), + ], + ), + ); + if (!context.mounted || !ownsCurrentForm()) return; + if (retry == true) { + widget.onSaved?.call(); + } else { + Navigator.of(context).pop(true); + } } else if (state.submissionStatus == ScheduleFormSubmissionStatus.failure) { + if (state.saveFailure == ScheduleSaveFailure.conflict) { + final l = AppLocalizations.of(context)!; + final review = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l.scheduleSaveConflict), + content: Text(l.scheduleSaveConflictBody), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(l.cancel), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(l.scheduleReviewCurrent), + ), + ], + ), + ); + if (!context.mounted || !ownsCurrentForm() || review != true) { + return; + } + try { + final current = await context + .read() + .reviewCurrent(); + if (!context.mounted || !ownsCurrentForm()) return; + final accept = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l.scheduleReviewCurrent), + content: SingleChildScrollView( + child: Text( + current.current == null + ? l.scheduleCreateReview + : '${current.current!.schedule.scheduleName}\n${current.current!.schedule.place.placeName}\n${current.current!.schedule.scheduleTime}\n${current.current!.preparation.ordered.preparationStepList.map((s) => '${s.preparationName}: ${s.preparationTime.inMinutes}').join('\n')}\n\n${l.scheduleDraftPreserved}', + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(l.cancel), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(l.ok), + ), + ], + ), + ); + if (context.mounted && ownsCurrentForm() && accept == true) { + context.read().acceptReviewedBaseline( + current.owner, + current.baseline, + current.current?.schedule, + ); + } + } catch (_) { + if (context.mounted && ownsCurrentForm()) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l.scheduleSaveUnavailable)), + ); + } + } + return; + } + final retry = await showModalBottomSheet( context: context, isScrollControlled: true, @@ -123,13 +225,16 @@ class _ScheduleMultiPageFormState extends State builder: (context) => SizedBox( height: MediaQuery.sizeOf(context).height * .94, child: RecurrenceSaveErrorSheet( - message: - state.submissionError ?? - AppLocalizations.of(context)!.error, + message: state.saveFailure == null + ? state.submissionError ?? + AppLocalizations.of(context)!.error + : state.saveFailure == ScheduleSaveFailure.protected + ? AppLocalizations.of(context)!.scheduleSaveProtected + : AppLocalizations.of(context)!.scheduleSaveUnavailable, ), ), ); - if (mounted && retry == true) widget.onSaved?.call(); + if (ownsCurrentForm() && retry == true) widget.onSaved?.call(); } }, child: BlocBuilder( diff --git a/pubspec.lock b/pubspec.lock index d3257aa6..859918c4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1130,7 +1130,7 @@ packages: source: hosted version: "0.6.8" sqlite3: - dependency: transitive + dependency: "direct dev" description: name: sqlite3 sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" diff --git a/pubspec.yaml b/pubspec.yaml index 8a7e22b9..e601b485 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -78,6 +78,7 @@ dependencies: dev_dependencies: + sqlite3: ^2.9.4 flutter_test: sdk: flutter # Exercise the locked iOS picker contract without replacing its UTI logic. diff --git a/test/core/backup/backup_service_test.dart b/test/core/backup/backup_service_test.dart index 76774736..bef11cee 100644 --- a/test/core/backup/backup_service_test.dart +++ b/test/core/backup/backup_service_test.dart @@ -268,12 +268,45 @@ void main() { .toList(); final deleted = generated[1]; await recurring.delete(deleted, RecurringEditScope.occurrence); + final oldStore = + (await database.select(database.users).getSingle()).storeIncarnation; + final oldSegments = await database + .select(database.recurringScheduleSegments) + .get(); final encrypted = await service.createEncryptedBackup(password); + final plaintext = utf8.decode( + await BackupCrypto( + sodiumLoader: loadSodiumForTest, + ).decrypt(container: encrypted, password: password), + ); + for (final internal in [ + 'storeIncarnation', + 'aggregateIncarnation', + 'aggregateVersion', + 'lastMutationId', + 'lastMutationDigest', + 'lastMutationVersion', + 'rootSegmentId', + ]) { + expect(plaintext, isNot(contains(internal))); + } final candidate = await service.previewEncryptedBackup( encrypted, password, ); await service.applyRestore(candidate); + expect( + (await database.select(database.users).getSingle()).storeIncarnation, + isNot(oldStore), + ); + final newSegments = await database + .select(database.recurringScheduleSegments) + .get(); + expect( + newSegments.first.aggregateIncarnation, + isNot(oldSegments.first.aggregateIncarnation), + ); + expect(newSegments.first.lastMutationId, isNull); await recurring.materialize(DateTime.utc(2030), DateTime.utc(2031)); final restored = (await database.scheduleDao.getScheduleList()) .map((r) => r.toScheduleEntity()) diff --git a/test/core/database/database_index_test.dart b/test/core/database/database_index_test.dart index 0043def6..4f129839 100644 --- a/test/core/database/database_index_test.dart +++ b/test/core/database/database_index_test.dart @@ -30,7 +30,7 @@ void main() { ); test('rejects in-place upgrades across the one-way local cutover', () async { - expect(database.schemaVersion, 2); + expect(database.schemaVersion, 3); await expectLater( database.migration.onUpgrade(database.createMigrator(), 0, 1), diff --git a/test/core/database/recurring_migration_test.dart b/test/core/database/recurring_migration_test.dart index 7ccc903d..7618fb09 100644 --- a/test/core/database/recurring_migration_test.dart +++ b/test/core/database/recurring_migration_test.dart @@ -1,11 +1,8 @@ +import 'package:sqlite3/sqlite3.dart'; import 'dart:io'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/core/database/database.dart'; -import 'package:on_time_front/data/mappers/domain_persistence_mappers.dart'; -import 'package:on_time_front/domain/entities/place_entity.dart'; -import 'package:on_time_front/domain/entities/schedule_entity.dart'; -import 'package:on_time_front/domain/entities/user_entity.dart'; void main() { test( @@ -15,52 +12,20 @@ void main() { 'ontime-migration-', ); final file = File('${directory.path}/v1.sqlite'); - var db = AppDatabase.forTesting(NativeDatabase(file)); + final raw = sqlite3.open(file.path); + raw.execute( + File('test/fixtures/database/schema_v1.sql').readAsStringSync(), + ); + raw.execute( + "INSERT INTO users(id,spare_time,note,eligible_outcome_count,on_time_outcome_count) VALUES('local-profile',0,'preserve',4,3)", + ); + raw.execute("INSERT INTO places(id,place_name) VALUES('p','Home')"); + raw.execute( + "INSERT INTO schedules(id,place_id,schedule_name,schedule_time,move_time,schedule_note) VALUES('old','p','Existing','2026-01-01T10:00:00.000',0,'keep')", + ); + raw.dispose(); + final db = AppDatabase.forTesting(NativeDatabase(file)); try { - await db.userDao.putUser( - const UserEntity( - id: 'local-profile', - spareTime: Duration.zero, - note: 'preserve', - eligibleOutcomeCount: 4, - onTimeOutcomeCount: 3, - ), - ); - await db.scheduleDao.createSchedule( - ScheduleEntity( - id: 'old', - place: const PlaceEntity(id: 'p', placeName: 'Home'), - scheduleName: 'Existing', - scheduleTime: DateTime(2026, 1, 1, 10), - moveTime: Duration.zero, - isChanged: false, - isStarted: false, - scheduleNote: 'keep', - scheduleSpareTime: Duration.zero, - ).toScheduleWithPlaceRow(), - ); - // The unchanged seven tables are schema 1 after removing the additive - // recurrence migration. Reopen through the real onUpgrade entry point. - for (final table in [ - 'recurring_schedule_exclusions', - 'recurring_schedule_segments', - 'preparation_definition_steps', - 'preparation_definitions', - ]) { - await db.customStatement('DROP TABLE $table'); - } - for (final column in [ - 'recurring_segment_id', - 'recurring_slot_key', - 'recurring_ordinal', - 'recurring_overrides', - 'preparation_definition_id', - ]) { - await db.customStatement('ALTER TABLE schedules DROP COLUMN $column'); - } - await db.customStatement('PRAGMA user_version = 1'); - await db.close(); - db = AppDatabase.forTesting(NativeDatabase(file)); expect( (await db.scheduleDao.getScheduleById('old')).schedule.scheduleName, 'Existing', diff --git a/test/core/database/schedule_aggregate_migration_test.dart b/test/core/database/schedule_aggregate_migration_test.dart new file mode 100644 index 00000000..0bafcfe2 --- /dev/null +++ b/test/core/database/schedule_aggregate_migration_test.dart @@ -0,0 +1,180 @@ +import 'dart:io'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sqlite3/sqlite3.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; + +void main() { + for (final version in [1, 2]) { + test( + 'frozen schema $version upgrades atomically and identities survive reopen/upsert', + () async { + final dir = await Directory.systemTemp.createTemp('a08-migration-'); + final file = File('${dir.path}/store.sqlite'); + final raw = sqlite3.open(file.path); + raw.execute( + File( + 'test/fixtures/database/schema_v$version.sql', + ).readAsStringSync(), + ); + raw.execute( + "INSERT INTO users(id,spare_time,note,data_revision,last_exported_revision,eligible_outcome_count,on_time_outcome_count) VALUES('local-profile',17,'preserved',9,8,4,3)", + ); + raw.execute("INSERT INTO places(id,place_name) VALUES('place','Home')"); + raw.execute( + "INSERT INTO schedules(id,place_id,schedule_name,schedule_time,move_time,is_started,started_at,finished_at,done_status,preparation_frozen,score_contribution_recorded) VALUES('schedule','place','Historical','2030-01-01T10:00:00.000',0,0,100,200,'normalEnd',1,1)", + ); + if (version == 2) { + raw.execute( + "INSERT INTO preparation_definitions(id,owner_id,scope,name,created_at) VALUES('definition','series','recurring','Owned',100)", + ); + raw.execute( + "INSERT INTO preparation_definition_steps(id,definition_id,name,minutes,position) VALUES('owned-step','definition','Owned step',0,0)", + ); + raw.execute( + "INSERT INTO recurring_schedule_segments(id,series_id,rule_json,schedule_json,preparation_id,from_slot,created_at) VALUES('root','series','{}','{}','definition','2030-01-01T10:00:00.000',100)", + ); + raw.execute( + "UPDATE schedules SET recurring_segment_id='root',recurring_slot_key='2030-01-01T10:00:00.000',recurring_ordinal=1,preparation_definition_id='definition' WHERE id='schedule'", + ); + } + raw.execute( + "INSERT INTO preparation_schedules(id,schedule_id,preparation_name,preparation_time) VALUES('two','schedule','Second',0)", + ); + raw.execute( + "INSERT INTO preparation_schedules(id,schedule_id,preparation_name,preparation_time,next_preparation_id) VALUES('one','schedule','First',12,'two')", + ); + raw.dispose(); + var db = AppDatabase.forTesting(NativeDatabase(file)); + try { + final profile = await db.select(db.users).getSingle(); + final row = await db.select(db.schedules).getSingle(); + expect(profile.storeIncarnation, hasLength(32)); + expect(row.aggregateIncarnation, hasLength(32)); + expect(row.aggregateVersion, 0); + if (version == 2) { + final segment = await db + .select(db.recurringScheduleSegments) + .getSingle(); + expect(segment.rootSegmentId, 'root'); + expect(row.recurringSegmentId, 'root'); + expect(row.preparationDefinitionId, 'definition'); + expect( + (await db.select(db.preparationDefinitionSteps).getSingle()) + .minutes, + 0, + ); + } + expect(profile.dataRevision, 9); + expect(profile.lastExportedRevision, 8); + expect(profile.spareTime, 17); + expect(row.startedAt?.millisecondsSinceEpoch, 100000); + expect(row.finishedAt?.millisecondsSinceEpoch, 200000); + expect(row.preparationFrozen, isTrue); + expect(row.scoreContributionRecorded, isTrue); + expect( + (await db.preparationScheduleDao + .getPreparationSchedulesByScheduleId('schedule')) + .ordered + .preparationStepList + .map((s) => s.id), + ['one', 'two'], + ); + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: 'overwrite attempt', + ), + ); + await db.close(); + db = AppDatabase.forTesting(NativeDatabase(file)); + expect( + (await db.select(db.users).getSingle()).storeIncarnation, + profile.storeIncarnation, + ); + expect( + (await db.select(db.schedules).getSingle()).aggregateIncarnation, + row.aggregateIncarnation, + ); + expect( + await db.customSelect('PRAGMA foreign_key_check').get(), + isEmpty, + ); + final fresh = AppDatabase.forTesting(NativeDatabase.memory()); + await fresh.customSelect('SELECT 1').get(); + for (final table in [ + 'users', + 'schedules', + 'recurring_schedule_segments', + ]) { + final oldColumns = + (await db.customSelect('PRAGMA table_info($table)').get()) + .map((r) => r.read('name')) + .toSet(); + final newColumns = + (await fresh.customSelect('PRAGMA table_info($table)').get()) + .map((r) => r.read('name')) + .toSet(); + expect(oldColumns, newColumns); + } + await fresh.close(); + } finally { + await db.close(); + await dir.delete(recursive: true); + } + }, + ); + } + test( + 'failed schema3 identity backfill rolls back additive columns and preserves schema2', + () async { + final dir = await Directory.systemTemp.createTemp('a08-migration-fault-'); + final file = File('${dir.path}/store.sqlite'); + final raw = sqlite3.open(file.path); + raw.execute( + File('test/fixtures/database/schema_v2.sql').readAsStringSync(), + ); + raw.execute( + "INSERT INTO users(id,spare_time,note) VALUES('local-profile',17,'keep')", + ); + raw.execute( + "CREATE TRIGGER reject_migration BEFORE UPDATE ON users BEGIN SELECT RAISE(ABORT,'injected migration fault'); END", + ); + raw.dispose(); + final db = AppDatabase.forTesting(NativeDatabase(file)); + await expectLater(db.select(db.users).get(), throwsA(anything)); + await db.close(); + final after = sqlite3.open(file.path); + expect(after.select('PRAGMA user_version').single['user_version'], 2); + expect( + after.select('PRAGMA table_info(users)').map((r) => r['name']), + isNot(contains('store_incarnation')), + ); + expect(after.select('SELECT note FROM users').single['note'], 'keep'); + after.dispose(); + await dir.delete(recursive: true); + }, + ); + test( + 'future database version is rejected without downgrade or data mutation', + () async { + final dir = await Directory.systemTemp.createTemp('a08-future-'); + final file = File('${dir.path}/store.sqlite'); + final raw = sqlite3.open(file.path); + raw.execute( + File('test/fixtures/database/schema_v2.sql').readAsStringSync(), + ); + raw.execute('PRAGMA user_version=99'); + raw.dispose(); + final db = AppDatabase.forTesting(NativeDatabase(file)); + await expectLater(db.select(db.users).get(), throwsA(anything)); + await db.close(); + final after = sqlite3.open(file.path); + expect(after.select('PRAGMA user_version').single['user_version'], 99); + after.dispose(); + await dir.delete(recursive: true); + }, + ); +} diff --git a/test/data/daos/schedule_dao_test.dart b/test/data/daos/schedule_dao_test.dart index d582d3ff..a507af0f 100644 --- a/test/data/daos/schedule_dao_test.dart +++ b/test/data/daos/schedule_dao_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/data/mappers/domain_persistence_mappers.dart'; import 'package:on_time_front/domain/entities/schedule_not_found.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -68,10 +69,37 @@ void main() async { ); }, ); + test( + 'watch preserves ascending schedule time for reverse insertion order', + () async { + for (final hour in [15, 10, 8]) { + await scheduleDao.createSchedule( + ScheduleWithPlace( + schedule: scheduleModel.copyWith( + id: 'at-$hour', + scheduleTime: DateTime(2030, 1, 1, hour), + ), + place: placeModel, + ), + ); + } + final rows = await scheduleDao.watchScheduleList().first; + expect(rows.map((r) => r.schedule.scheduleTime.hour), [8, 10, 15]); + expect( + rows.map((r) => r.schedule.aggregateIncarnation).toSet(), + hasLength(3), + ); + }, + ); group('createSchedule', () { test('should insert a schedule into the database', () async { final result = await scheduleDao.createSchedule(scheduleWithPlaceModel); - expect(result, equals(scheduleWithPlaceModel)); + expect( + result.toScheduleEntity(), + scheduleWithPlaceModel.toScheduleEntity(), + ); + expect(result.schedule.aggregateIncarnation, hasLength(32)); + expect(result.schedule.aggregateVersion, 0); }); }); @@ -93,7 +121,19 @@ void main() async { final result = await scheduleDao.updateSchedule(updatedScheduleModel); //assert - expect(result, equals(updatedScheduleModel)); + expect( + ScheduleWithPlace( + schedule: result, + place: placeModel, + ).toScheduleEntity(), + ScheduleWithPlace( + schedule: updatedScheduleModel, + place: placeModel, + ).toScheduleEntity(), + ); + expect(result.aggregateIncarnation, hasLength(32)); + expect(result.aggregateVersion, 1); + expect(result, (await scheduleDao.getScheduleById(result.id)).schedule); }); }); group('deleteSchedule', () { @@ -133,7 +173,12 @@ void main() async { final result = await scheduleDao.getScheduleById(scheduleModel.id); // - expect(result, equals(scheduleWithPlaceModel)); + expect( + result.toScheduleEntity(), + scheduleWithPlaceModel.toScheduleEntity(), + ); + expect(result.schedule.aggregateIncarnation, hasLength(32)); + expect(result.schedule.aggregateVersion, 0); }); }); @@ -199,7 +244,11 @@ void main() async { final result = await scheduleDao.getSchedulesByDate(startDate, endDate); //assert - expect(result, equals([scheduleWithPlaceModel])); + expect(result.map((row) => row.toScheduleEntity()), [ + scheduleWithPlaceModel.toScheduleEntity(), + ]); + expect(result.single.schedule.aggregateIncarnation, hasLength(32)); + expect(result.single.schedule.aggregateVersion, 0); }, ); @@ -231,10 +280,10 @@ void main() async { final result = await scheduleDao.getSchedulesByDate(startDate, null); //assert - expect( - result, - equals([scheduleWithPlaceModel, laterScheduleWithPlaceModel]), - ); + expect(result.map((row) => row.toScheduleEntity()), [ + scheduleWithPlaceModel.toScheduleEntity(), + laterScheduleWithPlaceModel.toScheduleEntity(), + ]); }, ); }); diff --git a/test/data/data_sources/schedule_local_data_source_test.dart b/test/data/data_sources/schedule_local_data_source_test.dart index fe5a8a95..49e3b7c6 100644 --- a/test/data/data_sources/schedule_local_data_source_test.dart +++ b/test/data/data_sources/schedule_local_data_source_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/data/mappers/domain_persistence_mappers.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/core/database/database.dart'; @@ -158,7 +159,11 @@ void main() { DateTime(2026, 5, 15), DateTime(2026, 5, 16), ); - expect(schedules, [keep]); + expect(schedules.map((row) => row.toScheduleEntity()), [ + keep.toScheduleEntity(), + ]); + expect(schedules.single.schedule.aggregateIncarnation, hasLength(32)); + expect(schedules.single.schedule.aggregateVersion, 0); }); } diff --git a/test/data/repositories/recurring_schedule_repository_test.dart b/test/data/repositories/recurring_schedule_repository_test.dart index f75c0a09..4b27cd24 100644 --- a/test/data/repositories/recurring_schedule_repository_test.dart +++ b/test/data/repositories/recurring_schedule_repository_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'package:drift/drift.dart' show Value; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -45,10 +46,13 @@ void main() { tearDown(() => db.close()); test( - 'series owns reusable preparation and create retries are idempotent', + 'series owns reusable preparation and direct duplicate create rejects missing intent receipt', () async { await repository.create(_schedule('a', start), _prep(), daily(count: 3)); - await repository.create(_schedule('a', start), _prep(), daily(count: 3)); + await expectLater( + repository.create(_schedule('a', start), _prep(), daily(count: 3)), + throwsA(isA()), + ); await repository.create( _schedule('b', start.add(const Duration(hours: 2))), _prep(), diff --git a/test/data/repositories/schedule_aggregate_repository_test.dart b/test/data/repositories/schedule_aggregate_repository_test.dart new file mode 100644 index 00000000..36132ef4 --- /dev/null +++ b/test/data/repositories/schedule_aggregate_repository_test.dart @@ -0,0 +1,970 @@ +import 'package:on_time_front/data/tables/schedule_with_place_model.dart'; +import 'dart:async'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/data/repositories/recurring_schedule_repository_impl.dart'; +import 'package:on_time_front/data/repositories/schedule_aggregate_repository_impl.dart'; +import 'package:on_time_front/data/repositories/schedule_repository_impl.dart'; +import 'package:on_time_front/domain/repositories/timed_preparation_repository.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_entity.dart'; +import 'package:on_time_front/domain/entities/place_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_step_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; +import 'package:on_time_front/domain/recurrence/recurrence_rule.dart'; +import 'package:on_time_front/domain/recurrence/recurring_schedule.dart'; +import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; +import 'package:on_time_front/domain/use-cases/schedule_save_workflow.dart'; +import 'package:on_time_front/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart'; + +class _Runtime extends Fake implements TimedPreparationRepository {} + +class _Effects extends Fake implements ScheduleMutationAlarmEffectsCoordinator { + int calls = 0; + Future Function()? handler; + @override + Future afterCommit() { + calls++; + return handler?.call() ?? Future.value(true); + } +} + +PreparationEntity prep([int minutes = 10]) => PreparationEntity( + preparationStepList: [ + PreparationStepEntity( + id: 'draft-step', + preparationName: 'Prepare', + preparationTime: Duration(minutes: minutes), + ), + ], +); +ScheduleEntity schedule([String id = 'one']) => ScheduleEntity( + id: id, + place: PlaceEntity(id: 'place-$id', placeName: 'Office'), + scheduleName: 'Meeting', + scheduleTime: DateTime.utc(2030, 1, 1, 10), + timeZoneId: 'UTC', + occurrenceOffsetSeconds: 0, + moveTime: Duration.zero, + isChanged: true, + isStarted: false, + scheduleSpareTime: Duration.zero, + scheduleNote: '', + preparationMode: SchedulePreparationMode.custom, +); +void main() { + late AppDatabase db; + late LocalDataOperationGate gate; + late ScheduleAggregateRepositoryImpl repository; + late RecurringScheduleRepositoryImpl recurring; + setUp(() async { + db = AppDatabase.forTesting(NativeDatabase.memory()); + gate = LocalDataOperationGate(); + recurring = RecurringScheduleRepositoryImpl( + db, + now: () => DateTime.utc(2029), + ); + repository = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: gate, + now: () => DateTime.utc(2029), + ); + await db.userDao.putUser( + const UserEntity(id: 'local-profile', spareTime: Duration.zero, note: ''), + ); + }); + tearDown(() async { + await db.close(); + }); + Future revision() async => + (await db.select(db.users).getSingle()).dataRevision; + Future create({ + String id = 'one', + String mutation = 'intent', + PreparationEntity? preparation, + ScheduleEntity? value, + RecurrenceRule? rule, + }) async => ScheduleFormSubmission( + schedule: value ?? schedule(id), + preparation: preparation ?? prep(), + preparationChanged: true, + baseline: await repository.newBaseline(), + mutationId: mutation, + recurrenceRule: rule, + ); + Future edit({ + String id = 'one', + String mutation = 'edit', + int minutes = 20, + String? name, + ScheduleEntity? value, + }) async { + final snapshot = await repository.readForEdit(id); + return ScheduleFormSubmission( + schedule: + value ?? + snapshot.schedule.copyWith( + scheduleName: name ?? snapshot.schedule.scheduleName, + ), + preparation: prep(minutes), + preparationChanged: true, + originalSchedule: snapshot.schedule, + baseline: snapshot.baseline, + mutationId: mutation, + ); + } + + final conflict = throwsA( + isA().having( + (e) => e.failure, + 'failure', + ScheduleSaveFailure.conflict, + ), + ); + for (final fault in ['place', 'schedule', 'step', 'revision']) { + test( + '$fault fault rolls back the whole aggregate and same intent retries', + () async { + final draft = await create(); + final before = await revision(); + final target = { + 'place': 'INSERT ON places', + 'schedule': 'INSERT ON schedules', + 'step': 'INSERT ON preparation_schedules', + 'revision': 'UPDATE OF data_revision ON users', + }[fault]; + await db.customStatement( + "CREATE TRIGGER reject_write BEFORE $target BEGIN SELECT RAISE(ABORT, 'injected'); END", + ); + await expectLater( + repository.save(draft, editing: false), + throwsA(anything), + ); + expect(await db.select(db.schedules).get(), isEmpty); + expect(await db.select(db.places).get(), isEmpty); + expect(await db.select(db.preparationSchedules).get(), isEmpty); + expect(await revision(), before); + await db.customStatement('DROP TRIGGER reject_write'); + await repository.save(draft, editing: false); + expect(await revision(), before + 1); + expect( + (await repository.readForEdit('one')).preparation.totalDuration, + const Duration(minutes: 10), + ); + }, + ); + } + test( + 'edit failure after old preparation deletion preserves old aggregate and revision', + () async { + await repository.save(await create(), editing: false); + final draft = await edit(name: 'Changed'); + final old = await repository.readForEdit('one'); + final before = await revision(); + await db.customStatement( + "CREATE TRIGGER reject_step BEFORE INSERT ON preparation_schedules BEGIN SELECT RAISE(ABORT, 'injected'); END", + ); + await expectLater( + repository.save(draft, editing: true), + throwsA(anything), + ); + final after = await repository.readForEdit('one'); + expect(after.schedule, old.schedule); + expect(after.preparation, old.preparation); + expect(after.baseline, old.baseline); + expect(await revision(), before); + }, + ); + test( + 'concurrent and recreated repository lost receipt retry commit once', + () async { + final draft = await create(); + final before = await revision(); + final receipts = await Future.wait([ + repository.save(draft, editing: false), + repository.save(draft, editing: false), + ]); + expect(receipts.where((r) => r.changed), hasLength(1)); + repository = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: gate, + now: () => DateTime.utc(2029), + ); + expect((await repository.save(draft, editing: false)).changed, isFalse); + expect(await revision(), before + 1); + expect(await db.select(db.schedules).get(), hasLength(1)); + }, + ); + test('same id other input conflicts instead of upsert', () async { + final draft = await create(); + await repository.save(draft, editing: false); + await expectLater( + repository.save( + await create(value: schedule().copyWith(scheduleName: 'Different')), + editing: false, + ), + conflict, + ); + }); + test('semantic no-op preserves owned step IDs and revision', () async { + await repository.save(await create(), editing: false); + final before = await revision(); + final old = await repository.readForEdit('one'); + final result = await repository.save( + await edit(minutes: 10), + editing: true, + ); + expect(result.changed, isFalse); + expect(await revision(), before); + expect((await repository.readForEdit('one')).preparation, old.preparation); + }); + test( + 'two edit drafts and value ABA conflict without using global revision', + () async { + await repository.save(await create(), editing: false); + final old = await edit(); + await repository.save(await edit(name: 'B'), editing: true); + await repository.save( + await edit(name: 'Meeting', mutation: 'back'), + editing: true, + ); + await expectLater(repository.save(old, editing: true), conflict); + final current = await edit(); + await db.userDao.updateSpareTime( + 'local-profile', + const Duration(minutes: 3), + ); + await repository.save(current, editing: true); + expect( + (await repository.readForEdit('one')).preparation.totalDuration, + const Duration(minutes: 20), + ); + }, + ); + test( + 'deleted create retry and recreated same id reject old intent', + () async { + final old = await create(); + await repository.save(old, editing: false); + await (db.delete( + db.preparationSchedules, + )..where((t) => t.scheduleId.equals('one'))).go(); + await (db.delete(db.schedules)..where((t) => t.id.equals('one'))).go(); + await db.userDao.markDurableDataChanged('local-profile'); + await expectLater(repository.save(old, editing: false), conflict); + await repository.save(await create(mutation: 'new'), editing: false); + await expectLater(repository.save(old, editing: false), conflict); + }, + ); + test( + 'new store same revision and fresh memory generation rejects old draft', + () async { + final old = await create(); + final revisionBefore = await revision(); + await db.deleteAllDurableData(); + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + ), + ); + expect(await revision(), revisionBefore); + repository = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: LocalDataOperationGate(), + now: () => DateTime.utc(2029), + ); + await expectLater(repository.save(old, editing: false), conflict); + }, + ); + test( + 'unrelated durable create conflict is explicit; reviewed new intent succeeds', + () async { + final old = await create(); + await db.userDao.markDurableDataChanged('local-profile'); + await expectLater(repository.save(old, editing: false), conflict); + await repository.save(await create(mutation: 'reviewed'), editing: false); + }, + ); + test( + 'start between draft and commit conflicts and preserves protected fields', + () async { + await repository.save(await create(), editing: false); + final old = await edit(); + final writer = ScheduleRepositoryImpl( + database: db, + timedPreparationRepository: _Runtime(), + ); + await writer.startSchedule('one', startedAt: DateTime.utc(2029)); + await expectLater(repository.save(old, editing: true), conflict); + final row = await db.select(db.schedules).getSingle(); + expect(row.isStarted, isTrue); + expect(row.preparationFrozen, isTrue); + expect(row.startedAt?.toUtc(), DateTime.utc(2029)); + await writer.dispose(); + }, + ); + test('same shared place label edit copies ownership', () async { + await repository.save(await create(), editing: false); + await repository.save( + await create( + id: 'two', + mutation: 'two', + value: schedule('two').copyWith(place: schedule().place), + ), + editing: false, + ); + final current = await repository.readForEdit('one'); + await repository.save( + await edit( + value: current.schedule.copyWith( + place: PlaceEntity( + id: current.schedule.place.id, + placeName: 'Changed', + ), + ), + ), + editing: true, + ); + expect( + (await repository.readForEdit('two')).schedule.place.placeName, + 'Office', + ); + }); + for (final invalid in [ + PreparationEntity(preparationStepList: []), + PreparationEntity( + preparationStepList: const [ + PreparationStepEntity( + id: 's', + preparationName: 'Step', + preparationTime: Duration(seconds: 119), + ), + ], + ), + ]) { + test( + 'invalid custom input is rejected atomically ${invalid.totalDuration}', + () async { + await expectLater( + repository.save(await create(preparation: invalid), editing: false), + throwsA(isA()), + ); + expect(await db.select(db.schedules).get(), isEmpty); + }, + ); + } + test('create cannot inject outcome or frozen metadata', () async { + await expectLater( + repository.save( + await create( + value: schedule().copyWith( + isStarted: true, + scoreContributionRecorded: true, + ), + ), + editing: false, + ), + throwsA(isA()), + ); + expect(await db.select(db.schedules).get(), isEmpty); + }); + test( + 'recurring duplicate create has durable receipt and rejects changed input', + () async { + final rule = RecurrenceRule( + frequency: RecurrenceFrequency.daily, + start: DateTime.utc(2030, 1, 1, 10), + timeZoneId: 'UTC', + count: 65, + ); + final draft = await create(rule: rule); + final before = await revision(); + await repository.save(draft, editing: false); + expect(await db.select(db.schedules).get(), hasLength(60)); + final root = await db.select(db.recurringScheduleSegments).getSingle(); + await recurring.materialize(DateTime.utc(2030), DateTime.utc(2031)); + expect(await db.select(db.schedules).get(), hasLength(65)); + final expanded = await db + .select(db.recurringScheduleSegments) + .getSingle(); + expect(expanded.aggregateVersion, root.aggregateVersion); + expect(expanded.lastMutationVersion, root.lastMutationVersion); + expect((await repository.save(draft, editing: false)).changed, isFalse); + expect(await revision(), before + 1); + await expectLater( + repository.save( + await create( + rule: rule, + value: schedule().copyWith(scheduleName: 'Other'), + ), + editing: false, + ), + conflict, + ); + }, + ); + test( + 'workflow failed delivery retry does not re-save and coalesces pending intent', + () async { + final effects = _Effects(); + final barrier = Completer(); + effects.handler = () => barrier.future; + final flow = ScheduleSaveWorkflow(repository, effects); + final draft = await create(); + final one = flow.save(draft, editing: false), + two = flow.save(draft, editing: false); + expect(identical(one, two), isTrue); + await Future.delayed(Duration.zero); + barrier.complete(false); + final receipt = await one; + expect(receipt.deliveryPending, isTrue); + final before = await revision(); + effects.handler = () => Future.value(true); + expect((await flow.retryDelivery(receipt)).deliveryPending, isFalse); + expect(await revision(), before); + expect(await db.select(db.schedules).get(), hasLength(1)); + }, + ); + test( + 'late delivery after replacement retains commit but never current success', + () async { + final effects = _Effects(); + final barrier = Completer(), entered = Completer(); + effects.handler = () { + entered.complete(); + return barrier.future; + }; + final flow = ScheduleSaveWorkflow(repository, effects); + final pending = flow.save(await create(), editing: false); + await entered.future; + await gate.run(() async {}, replacesData: true); + barrier.complete(true); + final receipt = await pending; + expect(receipt.deliveryPending, isTrue); + expect(repository.isCurrent(receipt), isFalse); + expect(await db.select(db.schedules).get(), hasLength(1)); + }, + ); + test( + 'custom to default to template preserves reference sources and owned cleanup', + () async { + await db.preparationUserDao.createPreparationUser( + prep(7), + 'local-profile', + ); + await db.preparationTemplateDao.put( + id: 'template', + name: 'Template', + preparation: prep(8), + now: DateTime.utc(2029), + ); + await repository.save(await create(), editing: false); + var snap = await repository.readForEdit('one'); + await repository.save( + ScheduleFormSubmission( + schedule: snap.schedule.copyWith( + preparationMode: SchedulePreparationMode.defaultPreparation, + isChanged: false, + ), + preparation: prep(7), + preparationChanged: false, + mutationId: 'default', + baseline: snap.baseline, + originalSchedule: snap.schedule, + ), + editing: true, + ); + expect(await db.select(db.preparationSchedules).get(), isEmpty); + expect( + (await repository.readForEdit('one')).preparation.totalDuration, + const Duration(minutes: 7), + ); + snap = await repository.readForEdit('one'); + await repository.save( + ScheduleFormSubmission( + schedule: snap.schedule.copyWith( + preparationMode: SchedulePreparationMode.template, + preparationTemplateId: 'template', + preparationTemplateName: 'Template', + ), + preparation: prep(8), + preparationChanged: false, + mutationId: 'template', + baseline: snap.baseline, + originalSchedule: snap.schedule, + ), + editing: true, + ); + final stale = await edit(); + await db.preparationTemplateDao.put( + id: 'template', + name: 'Template', + preparation: prep(9), + now: DateTime.utc(2029), + ); + await expectLater(repository.save(stale, editing: true), conflict); + expect( + (await db.preparationUserDao.getPreparationUsersByUserId( + 'local-profile', + )).totalDuration, + const Duration(minutes: 7), + ); + expect( + (await repository.readForEdit('one')).preparation.totalDuration, + const Duration(minutes: 9), + ); + }, + ); + test('default fallback dependency ABA invalidates stale edit', () async { + await db.preparationUserDao.createPreparationUser(prep(7), 'local-profile'); + await repository.save( + await create( + value: schedule().copyWith( + preparationMode: SchedulePreparationMode.defaultPreparation, + isChanged: false, + ), + ), + editing: false, + ); + final stale = await edit(); + await db.preparationUserDao.createPreparationUser(prep(9), 'local-profile'); + await db.preparationUserDao.createPreparationUser(prep(7), 'local-profile'); + await expectLater(repository.save(stale, editing: true), conflict); + }); + for (final table in [ + 'preparation_definition_steps', + 'recurring_schedule_segments', + 'schedules', + 'users', + ]) { + test( + 'recurring $table fault rolls back definitions series materialization and revision', + () async { + final rule = RecurrenceRule( + frequency: RecurrenceFrequency.daily, + start: DateTime.utc(2030, 1, 1, 10), + timeZoneId: 'UTC', + count: 3, + ); + final draft = await create(rule: rule); + final before = await revision(); + final event = table == 'users' ? 'UPDATE OF data_revision' : 'INSERT'; + await db.customStatement( + "CREATE TRIGGER fault BEFORE $event ON $table BEGIN SELECT RAISE(ABORT,'failure'); END", + ); + await expectLater( + repository.save(draft, editing: false), + throwsA(anything), + ); + expect(await db.select(db.schedules).get(), isEmpty); + expect(await db.select(db.recurringScheduleSegments).get(), isEmpty); + expect(await db.select(db.preparationDefinitions).get(), isEmpty); + expect(await revision(), before); + await db.customStatement('DROP TRIGGER fault'); + await repository.save(draft, editing: false); + expect(await revision(), before + 1); + }, + ); + } + test( + 'following split keeps stable root through clock reversal and recovers lost receipt', + () async { + var clock = DateTime.utc(2029); + recurring = RecurringScheduleRepositoryImpl(db, now: () => clock); + repository = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: gate, + now: () => clock, + ); + final rule = RecurrenceRule( + frequency: RecurrenceFrequency.daily, + start: DateTime.utc(2030, 1, 1, 10), + timeZoneId: 'UTC', + count: 3, + ); + await repository.save(await create(rule: rule), editing: false); + final rows = await db.scheduleDao.getScheduleList(); + final snapshot = await repository.readForEdit(rows[1].schedule.id); + final root = snapshot.baseline.rootId; + final draft = ScheduleFormSubmission( + schedule: snapshot.schedule.copyWith(scheduleName: 'New series'), + preparation: snapshot.preparation, + preparationChanged: false, + originalSchedule: snapshot.schedule, + baseline: snapshot.baseline, + mutationId: 'following', + recurringScope: RecurringEditScope.following, + recurrenceRule: rule.withStartAndEnd( + start: snapshot.schedule.scheduleTime, + count: rule.count, + until: rule.until, + ), + ); + clock = DateTime.utc(2028); + final before = await revision(); + await repository.save(draft, editing: true); + expect( + (await db.select(db.recurringScheduleSegments).get()) + .map((s) => s.rootSegmentId) + .toSet(), + {root}, + ); + repository = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: LocalDataOperationGate(), + now: () => clock, + ); + expect((await repository.save(draft, editing: true)).changed, isFalse); + expect(await revision(), before + 1); + expect( + (await db.scheduleDao.getScheduleList()).where( + (s) => s.schedule.scheduleName == 'New series', + ), + hasLength(2), + ); + }, + ); + test( + 'semantic same following save does not split or increase revision', + () async { + final rule = RecurrenceRule( + frequency: RecurrenceFrequency.daily, + start: DateTime.utc(2030, 1, 1, 10), + timeZoneId: 'UTC', + count: 3, + ); + await repository.save(await create(rule: rule), editing: false); + final snapshot = await repository.readForEdit( + (await db.scheduleDao.getScheduleList())[1].schedule.id, + ); + final before = await revision(); + final result = await repository.save( + ScheduleFormSubmission( + schedule: snapshot.schedule, + preparation: snapshot.preparation, + preparationChanged: false, + originalSchedule: snapshot.schedule, + baseline: snapshot.baseline, + mutationId: 'same-following', + recurringScope: RecurringEditScope.following, + recurrenceRule: rule.withStartAndEnd( + start: snapshot.schedule.scheduleTime, + count: rule.count, + until: rule.until, + ), + ), + editing: true, + ); + expect(result.changed, isFalse); + expect(await revision(), before); + expect(await db.select(db.recurringScheduleSegments).get(), hasLength(1)); + }, + ); + + test( + 'root receipt rejects dependency place mutation after following commit', + () async { + final rule = RecurrenceRule( + frequency: RecurrenceFrequency.daily, + start: DateTime.utc(2030, 1, 1, 10), + timeZoneId: 'UTC', + count: 3, + ); + await repository.save(await create(rule: rule), editing: false); + final original = await repository.readForEdit( + (await db.scheduleDao.getScheduleList())[1].schedule.id, + ); + final intent = ScheduleFormSubmission( + schedule: original.schedule.copyWith(scheduleName: 'Changed'), + preparation: original.preparation, + preparationChanged: false, + originalSchedule: original.schedule, + baseline: original.baseline, + mutationId: 'following', + recurringScope: RecurringEditScope.following, + recurrenceRule: rule.withStartAndEnd( + start: original.schedule.scheduleTime, + count: rule.count, + until: rule.until, + ), + ); + await repository.save(intent, editing: true); + final rootBefore = await (db.select( + db.recurringScheduleSegments, + )..where((t) => t.id.equals(original.baseline.rootId!))).getSingle(); + expect(rootBefore.lastMutationVersion, rootBefore.aggregateVersion); + await (db.update(db.places) + ..where((t) => t.id.equals(original.schedule.place.id))) + .write(const PlacesCompanion(placeName: Value('Renamed'))); + final rootAfter = await (db.select( + db.recurringScheduleSegments, + )..where((t) => t.id.equals(rootBefore.id))).getSingle(); + expect(rootAfter.aggregateVersion, isNot(rootBefore.aggregateVersion)); + await expectLater(repository.save(intent, editing: true), conflict); + }, + ); + for (final position in [0, 1, 2]) { + test( + 'custom step $position insertion failure has no partial aggregate', + () async { + final steps = PreparationEntity( + preparationStepList: List.generate( + 3, + (i) => PreparationStepEntity( + id: 'draft-$i', + preparationName: 'Step $i', + preparationTime: const Duration(minutes: 2), + nextPreparationId: i == 2 ? null : 'draft-${i + 1}', + ), + ), + ); + final intent = await create(preparation: steps); + final before = await revision(); + await db.customStatement( + "CREATE TRIGGER fail_position BEFORE INSERT ON preparation_schedules WHEN NEW.preparation_name='Step $position' BEGIN SELECT RAISE(ABORT,'step failure'); END", + ); + await expectLater( + repository.save(intent, editing: false), + throwsA(anything), + ); + expect(await db.select(db.schedules).get(), isEmpty); + expect(await db.select(db.places).get(), isEmpty); + expect(await db.select(db.preparationSchedules).get(), isEmpty); + expect(await revision(), before); + await db.customStatement('DROP TRIGGER fail_position'); + await repository.save(intent, editing: false); + expect(await db.select(db.preparationSchedules).get(), hasLength(3)); + expect(await revision(), before + 1); + }, + ); + } + test( + 'clock reaching preparation start protects unchanged database baseline', + () async { + await repository.save(await create(), editing: false); + final intent = await edit(); + repository = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: gate, + now: () => DateTime.utc(2030, 1, 1, 9, 50), + ); + await expectLater( + repository.save(intent, editing: true), + throwsA( + isA().having( + (e) => e.failure, + 'failure', + ScheduleSaveFailure.protected, + ), + ), + ); + }, + ); + for (final scope in [ + RecurringEditScope.occurrence, + RecurringEditScope.following, + ]) { + for (final target in ['preparation_definition_steps', 'users']) { + test( + 'recurring $scope $target failure preserves prior complete series', + () async { + final rule = RecurrenceRule( + frequency: RecurrenceFrequency.daily, + start: DateTime.utc(2030, 1, 1, 10), + timeZoneId: 'UTC', + count: 3, + ); + await repository.save(await create(rule: rule), editing: false); + final before = await repository.readForEdit( + (await db.scheduleDao.getScheduleList())[1].schedule.id, + ); + final revisionBefore = await revision(); + Future>>> rows() async => [ + for (final table in [ + 'schedules', + 'places', + 'preparation_definitions', + 'preparation_definition_steps', + 'recurring_schedule_segments', + 'recurring_schedule_exclusions', + ]) + (await db + .customSelect('SELECT * FROM $table ORDER BY rowid') + .get()) + .map((r) => r.data) + .toList(), + ]; + final original = await rows(); + final intent = ScheduleFormSubmission( + schedule: before.schedule.copyWith(scheduleName: 'Changed'), + preparation: prep(30), + preparationChanged: true, + originalSchedule: before.schedule, + baseline: before.baseline, + mutationId: 'edit', + recurringScope: scope, + recurrenceRule: scope == RecurringEditScope.following + ? rule.withStartAndEnd( + start: before.schedule.scheduleTime, + count: rule.count, + until: rule.until, + ) + : null, + ); + final operation = target == 'users' + ? 'UPDATE OF data_revision' + : 'INSERT'; + await db.customStatement( + "CREATE TRIGGER fail_edit BEFORE $operation ON $target BEGIN SELECT RAISE(ABORT,'failure'); END", + ); + await expectLater( + repository.save(intent, editing: true), + throwsA(anything), + ); + expect(await rows(), original); + expect(await revision(), revisionBefore); + await db.customStatement('DROP TRIGGER fail_edit'); + await repository.save(intent, editing: true); + expect(await revision(), revisionBefore + 1); + if (scope == RecurringEditScope.following) { + final latest = await repository.readForEdit( + (await db.scheduleDao.getScheduleList())[1].schedule.id, + ); + final segment = await recurring.getSegment( + latest.schedule.recurringSegmentId!, + ); + final same = ScheduleFormSubmission( + schedule: latest.schedule, + preparation: latest.preparation, + preparationChanged: false, + originalSchedule: latest.schedule, + baseline: latest.baseline, + mutationId: 'same', + recurringScope: scope, + recurrenceRule: segment.rule, + ); + expect( + (await repository.save(same, editing: true)).changed, + isFalse, + ); + expect(await revision(), revisionBefore + 1); + } + }, + ); + } + } + + test( + 'recreated create receipt cannot validate old baseline even with reused mutation identity', + () async { + final old = await create(); + await repository.save(old, editing: false); + await (db.delete( + db.preparationSchedules, + )..where((t) => t.scheduleId.equals('one'))).go(); + await (db.delete(db.schedules)..where((t) => t.id.equals('one'))).go(); + await db.userDao.markDurableDataChanged('local-profile'); + final replacement = await create(); + await repository.save(replacement, editing: false); + await expectLater(repository.save(old, editing: false), conflict); + }, + ); + test( + 'occurrence owned definition invalidates series root receipt dependency', + () async { + final rule = RecurrenceRule( + frequency: RecurrenceFrequency.daily, + start: DateTime.utc(2030, 1, 1, 10), + timeZoneId: 'UTC', + count: 3, + ); + await repository.save(await create(rule: rule), editing: false); + final rows = await db.scheduleDao.getScheduleList(); + final second = await repository.readForEdit(rows[1].schedule.id); + await repository.save( + ScheduleFormSubmission( + schedule: second.schedule, + preparation: prep(30), + preparationChanged: true, + originalSchedule: second.schedule, + baseline: second.baseline, + mutationId: 'occurrence', + ), + editing: true, + ); + final first = await repository.readForEdit(rows.first.schedule.id); + final intent = ScheduleFormSubmission( + schedule: first.schedule, + preparation: first.preparation, + preparationChanged: false, + originalSchedule: first.schedule, + baseline: first.baseline, + mutationId: 'following-no-op', + recurringScope: RecurringEditScope.following, + recurrenceRule: rule, + ); + await repository.save(intent, editing: true); + final changed = await repository.readForEdit(second.schedule.id); + await (db.update(db.preparationDefinitionSteps)..where( + (t) => t.definitionId.equals( + changed.schedule.preparationDefinitionId!, + ), + )) + .write(const PreparationDefinitionStepsCompanion(minutes: Value(31))); + await expectLater(repository.save(intent, editing: true), conflict); + }, + ); + for (final withPlace in [false, true]) { + test( + 'legacy persisted row withPlace=$withPlace cannot rewind identity version or receipt', + () async { + await repository.save(await create(), editing: false); + final old = await db.scheduleDao.getScheduleById('one'); + final intent = await edit(name: 'Changed'); + await repository.save(intent, editing: true); + final committed = await db.scheduleDao.getScheduleById('one'); + final replay = old.schedule.copyWith(scheduleName: 'Legacy change'); + if (withPlace) { + await db.scheduleDao.updateScheduleWithPlace( + ScheduleWithPlace(schedule: replay, place: old.place), + ); + } else { + await db.scheduleDao.updateSchedule(replay); + } + final after = await db.scheduleDao.getScheduleById('one'); + expect( + after.schedule.aggregateIncarnation, + committed.schedule.aggregateIncarnation, + ); + expect( + after.schedule.aggregateVersion, + greaterThan(committed.schedule.aggregateVersion!), + ); + expect( + after.schedule.lastMutationId, + committed.schedule.lastMutationId, + ); + await db.scheduleDao.updateSchedule(old.schedule); + final aba = await db.scheduleDao.getScheduleById('one'); + expect( + aba.schedule.aggregateVersion, + greaterThan(after.schedule.aggregateVersion!), + ); + await expectLater(repository.save(intent, editing: true), conflict); + }, + ); + } +} diff --git a/test/data/repositories/schedule_aggregate_snapshot_test.dart b/test/data/repositories/schedule_aggregate_snapshot_test.dart new file mode 100644 index 00000000..5b569ca2 --- /dev/null +++ b/test/data/repositories/schedule_aggregate_snapshot_test.dart @@ -0,0 +1,603 @@ +import 'dart:convert'; +import 'package:on_time_front/core/backup/backup_service.dart'; +import 'package:on_time_front/core/backup/backup_crypto.dart'; +import 'package:on_time_front/core/services/app_metadata_service.dart'; +import '../../helpers/noop_alarm_cleanup.dart'; +import '../../helpers/sodium_test_loader.dart'; +import 'package:on_time_front/domain/recurrence/recurring_schedule.dart'; +import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart'; +import 'package:on_time_front/domain/recurrence/recurrence_rule.dart'; +import 'dart:async'; +import 'dart:io'; +import 'package:drift/drift.dart' hide isNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/data/repositories/schedule_aggregate_repository_impl.dart'; +import 'package:on_time_front/data/repositories/recurring_schedule_repository_impl.dart'; +import 'package:on_time_front/data/repositories/alarm_repository_impl.dart'; +import 'package:on_time_front/data/repositories/preparation_repository_impl.dart'; +import 'package:on_time_front/data/data_sources/preparation_local_data_source.dart'; +import 'package:on_time_front/domain/repositories/preparation_repository.dart'; +import 'package:on_time_front/domain/repositories/schedule_repository.dart'; +import 'package:on_time_front/domain/repositories/user_repository.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; +import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; +import 'schedule_aggregate_repository_test.dart' show schedule, prep; + +class _BlockedRead extends QueryInterceptor { + String needle = 'FROM "preparation_schedules"'; + bool armed = false; + final entered = Completer(); + final release = Completer(); + @override + Future>> runSelect( + QueryExecutor executor, + String statement, + List args, + ) async { + if (armed && statement.contains(needle)) { + armed = false; + entered.complete(); + await release.future; + } + return executor.runSelect(statement, args); + } +} + +class _Schedules extends Fake implements ScheduleRepository {} + +class _Preparations extends Fake implements PreparationRepository {} + +class _Profile extends Fake implements UserRepository {} + +class _Metadata implements AppMetadataProvider { + @override + Future getMetadata() async => + const AppMetadata(version: '1', buildNumber: '1'); +} + +void main() { + for (final reader in ['edit', 'planning']) { + test( + '$reader snapshot cannot combine old schedule with new preparation', + () async { + final barrier = _BlockedRead(); + final db = AppDatabase.forTesting( + NativeDatabase.memory().interceptWith(barrier), + ); + final recurring = RecurringScheduleRepositoryImpl( + db, + now: () => DateTime.utc(2029), + ); + final repo = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: LocalDataOperationGate(), + now: () => DateTime.utc(2029), + ); + try { + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + ), + ); + await repo.save( + ScheduleFormSubmission( + schedule: schedule(), + preparation: prep(), + preparationChanged: true, + mutationId: 'create', + baseline: await repo.newBaseline(), + ), + editing: false, + ); + final baseline = await repo.readForEdit('one'); + final update = ScheduleFormSubmission( + schedule: baseline.schedule.copyWith(scheduleName: 'New'), + preparation: prep(30), + preparationChanged: true, + originalSchedule: baseline.schedule, + mutationId: 'edit', + baseline: baseline.baseline, + ); + final alarms = AlarmRepositoryImpl( + database: db, + scheduleRepository: _Schedules(), + preparationRepository: _Preparations(), + ); + barrier.armed = true; + final reading = reader == 'edit' + ? repo + .readForEdit('one') + .then( + (s) => ( + s.schedule.scheduleName, + s.preparation.totalDuration.inMinutes, + ), + ) + : alarms + .getAlarmWindow(DateTime.utc(2029), DateTime.utc(2031)) + .then( + (s) => ( + s.single.scheduleName, + s.single.preparation.totalDuration.inMinutes, + ), + ); + await barrier.entered.future; + final writing = repo.save(update, editing: true); + barrier.release.complete(); + expect(await reading, ('Meeting', 10)); + await writing; + final after = await repo.readForEdit('one'); + expect( + ( + after.schedule.scheduleName, + after.preparation.totalDuration.inMinutes, + ), + ('New', 30), + ); + } finally { + await db.close(); + } + }, + ); + } + test( + 'file database close/reopen with new gate verifies committed receipt', + () async { + final dir = await Directory.systemTemp.createTemp('a08-receipt-'); + final file = File('${dir.path}/local.sqlite'); + var db = AppDatabase.forTesting(NativeDatabase(file)); + var recurring = RecurringScheduleRepositoryImpl( + db, + now: () => DateTime.utc(2029), + ); + var repo = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: LocalDataOperationGate(), + now: () => DateTime.utc(2029), + ); + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + ), + ); + final draft = ScheduleFormSubmission( + schedule: schedule(), + preparation: prep(), + preparationChanged: true, + mutationId: 'persisted-intent', + baseline: await repo.newBaseline(), + ); + await repo.save(draft, editing: false); + final revision = (await db.select(db.users).getSingle()).dataRevision; + await db.close(); + db = AppDatabase.forTesting(NativeDatabase(file)); + recurring = RecurringScheduleRepositoryImpl( + db, + now: () => DateTime.utc(2029), + ); + repo = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: LocalDataOperationGate(), + now: () => DateTime.utc(2029), + ); + try { + expect((await repo.save(draft, editing: false)).changed, isFalse); + expect((await db.select(db.users).getSingle()).dataRevision, revision); + } finally { + await db.close(); + await dir.delete(recursive: true); + } + }, + ); + test( + 'real preparation watch sees committed aggregate and never rolled back value', + () async { + final db = AppDatabase.forTesting(NativeDatabase.memory()); + final recurring = RecurringScheduleRepositoryImpl( + db, + now: () => DateTime.utc(2029), + ); + final repo = ScheduleAggregateRepositoryImpl( + db, + recurring, + now: () => DateTime.utc(2029), + ); + final preparations = PreparationRepositoryImpl( + preparationLocalDataSource: PreparationLocalDataSourceImpl( + appDatabase: db, + ), + userRepository: _Profile(), + database: db, + ); + final values = []; + final subscription = preparations.preparationStream.listen((v) { + if (v['one'] != null) values.add(v['one']!.totalDuration.inMinutes); + }); + try { + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + ), + ); + final created = preparations.preparationStream.firstWhere( + (v) => v['one']?.totalDuration.inMinutes == 10, + ); + await repo.save( + ScheduleFormSubmission( + schedule: schedule(), + preparation: prep(), + preparationChanged: true, + mutationId: 'create', + baseline: await repo.newBaseline(), + ), + editing: false, + ); + await created; + final old = await repo.readForEdit('one'); + final draft = ScheduleFormSubmission( + schedule: old.schedule, + preparation: prep(99), + preparationChanged: true, + mutationId: 'edit', + baseline: old.baseline, + originalSchedule: old.schedule, + ); + await db.customStatement( + "CREATE TRIGGER fail_revision BEFORE UPDATE OF data_revision ON users BEGIN SELECT RAISE(ABORT,'failed'); END", + ); + await expectLater(repo.save(draft, editing: true), throwsA(anything)); + await db.customStatement('DROP TRIGGER fail_revision'); + // A subsequent committed dependency write drains the serial real watch reader. + // It cannot repair or hide an earlier invalid emission, retained in values. + final drained = preparations.preparationStream + .skip(1) + .firstWhere((v) => v['one']?.totalDuration.inMinutes == 10); + await db.preparationUserDao.createPreparationUser( + prep(7), + 'local-profile', + ); + await drained; + expect( + values, + isNot(contains(99)), + reason: 'rollback must not publish the attempted preparation', + ); + final committed = preparations.preparationStream.firstWhere( + (v) => v['one']?.totalDuration.inMinutes == 99, + ); + await repo.save(draft, editing: true); + await committed; + expect(values.first, 10); + expect(values.where((v) => v == 99).length, 1); + } finally { + await subscription.cancel(); + await preparations.dispose(); + await db.close(); + } + }, + ); + test( + 'template and definition trigger versions wake actual dependent schedule watch', + () async { + final db = AppDatabase.forTesting(NativeDatabase.memory()); + final recurring = RecurringScheduleRepositoryImpl( + db, + now: () => DateTime.utc(2029), + ); + final repo = ScheduleAggregateRepositoryImpl( + db, + recurring, + now: () => DateTime.utc(2029), + ); + try { + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + ), + ); + await db.preparationTemplateDao.put( + id: 'template', + name: 'Template', + preparation: prep(8), + now: DateTime.utc(2029), + ); + await repo.save( + ScheduleFormSubmission( + schedule: schedule().copyWith( + preparationMode: SchedulePreparationMode.template, + preparationTemplateId: 'template', + ), + preparation: prep(8), + preparationChanged: false, + mutationId: 'template-create', + baseline: await repo.newBaseline(), + ), + editing: false, + ); + final initial = await repo.readForEdit('one'); + final observed = db.scheduleDao + .watchScheduleList() + .asyncMap((_) => repo.readForEdit('one')) + .firstWhere((s) => s.baseline.version != initial.baseline.version); + await db.preparationTemplateDao.put( + id: 'template', + name: 'Template', + preparation: prep(9), + now: DateTime.utc(2029), + ); + expect((await observed).preparation.totalDuration.inMinutes, 9); + final rule = RecurrenceRule( + frequency: RecurrenceFrequency.daily, + start: DateTime.utc(2031, 1, 1, 10), + timeZoneId: 'UTC', + count: 2, + ); + await repo.save( + ScheduleFormSubmission( + schedule: schedule('series').copyWith(scheduleTime: rule.start), + preparation: prep(), + preparationChanged: true, + recurrenceRule: rule, + mutationId: 'series-create', + baseline: await repo.newBaseline(), + ), + editing: false, + ); + final row = (await db.scheduleDao.getScheduleList()).firstWhere( + (r) => r.schedule.recurringSegmentId != null, + ); + final before = await repo.readForEdit(row.schedule.id); + final updated = db.scheduleDao + .watchScheduleList() + .asyncMap((_) => repo.readForEdit(row.schedule.id)) + .firstWhere( + (s) => s.baseline.rootVersion != before.baseline.rootVersion, + ); + await (db.update(db.preparationDefinitionSteps)..where( + (t) => + t.definitionId.equals(row.schedule.preparationDefinitionId!), + )) + .write( + const PreparationDefinitionStepsCompanion(minutes: Value(11)), + ); + final after = await updated; + expect(after.preparation.totalDuration.inMinutes, 11); + expect(after.baseline.version, isNot(before.baseline.version)); + } finally { + await db.close(); + } + }, + ); + + test( + 'new draft reads default preparation and revision in one real transaction', + () async { + final barrier = _BlockedRead()..needle = 'FROM "preparation_users"'; + final db = AppDatabase.forTesting( + NativeDatabase.memory().interceptWith(barrier), + ); + final repo = ScheduleAggregateRepositoryImpl( + db, + RecurringScheduleRepositoryImpl(db), + gate: LocalDataOperationGate(), + ); + try { + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + ), + ); + await db.preparationUserDao.createPreparationUser( + prep(7), + 'local-profile', + ); + final revision = (await db.select(db.users).getSingle()).dataRevision; + barrier.armed = true; + final pending = repo.newDraft(); + await barrier.entered.future; + final writing = db.transaction(() async { + await db.preparationUserDao.createPreparationUser( + prep(9), + 'local-profile', + ); + await db.userDao.markDurableDataChanged('local-profile'); + }); + barrier.release.complete(); + final draft = await pending; + expect(draft.preparation.totalDuration.inMinutes, 7); + expect(draft.baseline.revision, revision); + await writing; + final current = await repo.newDraft(); + expect(current.preparation.totalDuration.inMinutes, 9); + expect(current.baseline.revision, revision + 1); + } finally { + await db.close(); + } + }, + ); + + test( + 'following receipt survives file close reopen and deletion of original occurrence', + () async { + final dir = await Directory.systemTemp.createTemp('a08-following-'); + final file = File('${dir.path}/db.sqlite'); + var db = AppDatabase.forTesting(NativeDatabase(file)); + var recurring = RecurringScheduleRepositoryImpl( + db, + now: () => DateTime.utc(2029), + ); + var repo = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: LocalDataOperationGate(), + now: () => DateTime.utc(2029), + ); + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + ), + ); + final rule = RecurrenceRule( + frequency: RecurrenceFrequency.daily, + start: DateTime.utc(2030, 1, 1, 10), + timeZoneId: 'UTC', + count: 3, + ); + await repo.save( + ScheduleFormSubmission( + schedule: schedule('series'), + preparation: prep(), + preparationChanged: true, + recurrenceRule: rule, + mutationId: 'create', + baseline: await repo.newBaseline(), + ), + editing: false, + ); + final before = await repo.readForEdit( + (await db.scheduleDao.getScheduleList())[1].schedule.id, + ); + final intent = ScheduleFormSubmission( + schedule: before.schedule.copyWith(scheduleName: 'New series'), + preparation: before.preparation, + preparationChanged: false, + originalSchedule: before.schedule, + baseline: before.baseline, + mutationId: 'split', + recurringScope: RecurringEditScope.following, + recurrenceRule: rule.withStartAndEnd( + start: before.schedule.scheduleTime, + count: rule.count, + until: rule.until, + ), + ); + await repo.save(intent, editing: true); + expect( + await (db.select( + db.schedules, + )..where((t) => t.id.equals(before.schedule.id))).getSingleOrNull(), + isNull, + ); + final revision = (await db.select(db.users).getSingle()).dataRevision; + await db.close(); + db = AppDatabase.forTesting(NativeDatabase(file)); + recurring = RecurringScheduleRepositoryImpl( + db, + now: () => DateTime.utc(2029), + ); + repo = ScheduleAggregateRepositoryImpl( + db, + recurring, + gate: LocalDataOperationGate(), + now: () => DateTime.utc(2029), + ); + try { + expect((await repo.save(intent, editing: true)).changed, isFalse); + expect((await db.select(db.users).getSingle()).dataRevision, revision); + expect(await db.select(db.schedules).get(), hasLength(3)); + } finally { + await db.close(); + await dir.delete(recursive: true); + } + }, + ); + test( + 'real encrypted backup cutoff cannot mix concurrent aggregate commit', + () async { + final barrier = _BlockedRead(); + final db = AppDatabase.forTesting( + NativeDatabase.memory().interceptWith(barrier), + ); + final repo = ScheduleAggregateRepositoryImpl( + db, + RecurringScheduleRepositoryImpl(db), + gate: LocalDataOperationGate(), + now: () => DateTime.utc(2029), + ); + final crypto = BackupCrypto(sodiumLoader: loadSodiumForTest); + final service = BackupService( + db, + _Metadata(), + NoopAlarmCleanup(), + crypto: crypto, + operationGate: LocalDataOperationGate(), + ); + try { + await db.userDao.putUser( + const UserEntity( + id: 'local-profile', + spareTime: Duration.zero, + note: '', + ), + ); + await repo.save( + ScheduleFormSubmission( + schedule: schedule(), + preparation: prep(), + preparationChanged: true, + mutationId: 'create', + baseline: await repo.newBaseline(), + ), + editing: false, + ); + final snapshot = await repo.readForEdit('one'); + final revision = (await db.select(db.users).getSingle()).dataRevision; + barrier.armed = true; + final reading = service.createEncryptedBackup( + 'portable backup password', + ); + await barrier.entered.future; + final writing = repo.save( + ScheduleFormSubmission( + schedule: snapshot.schedule.copyWith(scheduleName: 'New'), + preparation: prep(30), + preparationChanged: true, + mutationId: 'edit', + baseline: snapshot.baseline, + originalSchedule: snapshot.schedule, + ), + editing: true, + ); + barrier.release.complete(); + final bytes = await reading; + await writing; + final data = + jsonDecode( + utf8.decode( + await crypto.decrypt( + container: bytes, + password: 'portable backup password', + ), + ), + ) + as Map; + expect(data['dataRevision'], revision); + expect(data['schedules'].single['name'], 'Meeting'); + expect(data['schedulePreparations']['one'].single['minutes'], 10); + final actual = await repo.readForEdit('one'); + expect(actual.schedule.scheduleName, 'New'); + expect(actual.preparation.totalDuration.inMinutes, 30); + } finally { + await db.close(); + } + }, + ); +} diff --git a/test/domain/use-cases/create_schedule_form_submission_use_case_test.dart b/test/domain/use-cases/create_schedule_form_submission_use_case_test.dart index 304b7788..1b3be16b 100644 --- a/test/domain/use-cases/create_schedule_form_submission_use_case_test.dart +++ b/test/domain/use-cases/create_schedule_form_submission_use_case_test.dart @@ -1,80 +1,53 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:on_time_front/domain/entities/place_entity.dart'; -import 'package:on_time_front/domain/entities/preparation_entity.dart'; -import 'package:on_time_front/domain/entities/preparation_step_entity.dart'; -import 'package:on_time_front/domain/entities/schedule_entity.dart'; -import 'package:on_time_front/domain/use-cases/create_custom_preparation_use_case.dart'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'package:on_time_front/domain/use-cases/create_schedule_form_submission_use_case.dart'; -import 'package:on_time_front/domain/use-cases/create_schedule_with_place_use_case.dart'; +import 'package:on_time_front/domain/use-cases/schedule_save_workflow.dart'; import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; +import '../../data/repositories/schedule_aggregate_repository_test.dart' + show schedule, prep; -class SpyCreateScheduleWithPlaceUseCase - implements CreateScheduleWithPlaceUseCase { - final createdSchedules = []; - +class Workflow extends Fake implements ScheduleSaveWorkflow { + bool? editing; + int saves = 0; + int retries = 0; @override - Future call(ScheduleEntity schedule) async { - createdSchedules.add(schedule); + Future save( + ScheduleFormSubmission value, { + required bool editing, + }) async { + this.editing = editing; + saves++; + return ScheduleSaveReceipt( + scheduleId: value.schedule.id, + mutationId: 'intent', + generation: 0, + changed: true, + deliveryPending: true, + ); } -} - -class SpyCreateCustomPreparationUseCase - implements CreateCustomPreparationUseCase { - final createdPreparations = <({PreparationEntity preparation, String id})>[]; @override - Future call( - PreparationEntity preparationEntity, - String scheduleId, - ) async { - createdPreparations.add((preparation: preparationEntity, id: scheduleId)); + Future retryDelivery(ScheduleSaveReceipt receipt) async { + retries++; + return receipt.withDeliveryPending(false); } } void main() { - test( - 'changed preparation creates schedule and saves custom preparation locally', - () async { - final createScheduleUseCase = SpyCreateScheduleWithPlaceUseCase(); - final createCustomPreparationUseCase = - SpyCreateCustomPreparationUseCase(); - final useCase = CreateScheduleFormSubmissionUseCase( - createScheduleUseCase, - createCustomPreparationUseCase, - ); - final schedule = ScheduleEntity( - id: 'schedule-1', - place: PlaceEntity(id: 'place-1', placeName: 'Office'), - scheduleName: 'Meeting', - scheduleTime: DateTime(2027, 3, 20, 9), - moveTime: const Duration(minutes: 30), - isChanged: true, - isStarted: false, - scheduleSpareTime: const Duration(minutes: 10), - scheduleNote: '', - ); - final preparation = PreparationEntity( - preparationStepList: const [ - PreparationStepEntity( - id: 'prep-1', - preparationName: 'Shower', - preparationTime: Duration(minutes: 10), - ), - ], - ); - - await useCase( - ScheduleFormSubmission( - schedule: schedule, - preparation: preparation, - preparationChanged: true, - ), - ); - - expect(createScheduleUseCase.createdSchedules, [schedule]); - expect(createCustomPreparationUseCase.createdPreparations, [ - (preparation: preparation, id: 'schedule-1'), - ]); - }, - ); + test('create preserves committed result and retries delivery only', () async { + final workflow = Workflow(); + final useCase = CreateScheduleFormSubmissionUseCase(workflow); + final receipt = await useCase( + ScheduleFormSubmission( + schedule: schedule(), + preparation: prep(), + preparationChanged: true, + ), + ); + expect(workflow.editing, false); + expect(receipt.deliveryPending, isTrue); + expect((await useCase.retryDelivery(receipt)).deliveryPending, isFalse); + expect(workflow.saves, 1); + expect(workflow.retries, 1); + }); } diff --git a/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart b/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart index 7f203a7b..7fea005c 100644 --- a/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart +++ b/test/domain/use-cases/update_schedule_form_submission_use_case_test.dart @@ -1,177 +1,53 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:on_time_front/domain/entities/place_entity.dart'; -import 'package:on_time_front/domain/entities/preparation_entity.dart'; -import 'package:on_time_front/domain/entities/preparation_step_entity.dart'; -import 'package:on_time_front/domain/entities/schedule_entity.dart'; -import 'package:on_time_front/domain/entities/schedule_preparation_mode.dart'; -import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; -import 'package:on_time_front/domain/use-cases/update_preparation_by_schedule_id_use_case.dart'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'package:on_time_front/domain/use-cases/update_schedule_form_submission_use_case.dart'; -import 'package:on_time_front/domain/use-cases/update_schedule_use_case.dart'; - -class SpyUpdateScheduleUseCase implements UpdateScheduleUseCase { - final updatedSchedules = []; +import 'package:on_time_front/domain/use-cases/schedule_save_workflow.dart'; +import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; +import '../../data/repositories/schedule_aggregate_repository_test.dart' + show schedule, prep; +class Workflow extends Fake implements ScheduleSaveWorkflow { + bool? editing; + int saves = 0; + int retries = 0; @override - Future call( - ScheduleEntity schedule, { - bool includePreparationSource = false, + Future save( + ScheduleFormSubmission value, { + required bool editing, }) async { - updatedSchedules.add(schedule); + this.editing = editing; + saves++; + return ScheduleSaveReceipt( + scheduleId: value.schedule.id, + mutationId: 'intent', + generation: 0, + changed: true, + deliveryPending: true, + ); } -} - -class SpyUpdatePreparationByScheduleIdUseCase - implements UpdatePreparationByScheduleIdUseCase { - final updatedPreparations = <({PreparationEntity preparation, String id})>[]; @override - Future call( - PreparationEntity preparationEntity, - String scheduleId, - ) async { - updatedPreparations.add((preparation: preparationEntity, id: scheduleId)); + Future retryDelivery(ScheduleSaveReceipt receipt) async { + retries++; + return receipt.withDeliveryPending(false); } } void main() { - ScheduleEntity schedule({bool isChanged = false}) { - return ScheduleEntity( - id: 'schedule-1', - place: PlaceEntity(id: 'place-1', placeName: 'Office'), - scheduleName: 'Meeting', - scheduleTime: DateTime(2027, 3, 20, 9), - moveTime: const Duration(minutes: 30), - isChanged: isChanged, - isStarted: false, - scheduleSpareTime: const Duration(minutes: 10), - scheduleNote: '', - ); - } - - test( - 'unchanged preparation updates the schedule without saving preparation', - () async { - final updateScheduleUseCase = SpyUpdateScheduleUseCase(); - final updatePreparationUseCase = - SpyUpdatePreparationByScheduleIdUseCase(); - final useCase = UpdateScheduleFormSubmissionUseCase( - updateScheduleUseCase, - updatePreparationUseCase, - ); - final targetSchedule = schedule(); - final preparation = PreparationEntity( - preparationStepList: const [ - PreparationStepEntity( - id: 'prep-1', - preparationName: 'Shower', - preparationTime: Duration(minutes: 10), - ), - ], - ); - - await useCase( - ScheduleFormSubmission( - schedule: targetSchedule, - preparation: preparation, - preparationChanged: false, - ), - ); - - expect(updateScheduleUseCase.updatedSchedules, [targetSchedule]); - expect(updatePreparationUseCase.updatedPreparations, isEmpty); - }, - ); - - test( - 'changed default preparation is copied with fresh ordered step IDs', - () async { - final updateScheduleUseCase = SpyUpdateScheduleUseCase(); - final updatePreparationUseCase = - SpyUpdatePreparationByScheduleIdUseCase(); - final generatedIds = ['new-step-1', 'new-step-2'].iterator; - final useCase = UpdateScheduleFormSubmissionUseCase.withIdGenerator( - updateScheduleUseCase, - updatePreparationUseCase, - newId: () { - generatedIds.moveNext(); - return generatedIds.current; - }, - ); - const preparation = PreparationEntity( - preparationStepList: [ - PreparationStepEntity( - id: 'default-step-2', - preparationName: 'Bathroom', - preparationTime: Duration(minutes: 5), - ), - PreparationStepEntity( - id: 'default-step-1', - preparationName: 'Makeup', - preparationTime: Duration(minutes: 20), - nextPreparationId: 'default-step-2', - ), - ], - ); - - await useCase( - ScheduleFormSubmission( - schedule: schedule(isChanged: true), - preparation: preparation, - preparationChanged: true, - originalPreparationMode: SchedulePreparationMode.defaultPreparation, - ), - ); - - final updated = - updatePreparationUseCase.updatedPreparations.single.preparation; - final steps = updated.preparationStepList; - expect(steps.map((step) => step.id), ['new-step-1', 'new-step-2']); - expect(steps.map((step) => step.preparationName), ['Makeup', 'Bathroom']); - expect(steps[0].nextPreparationId, 'new-step-2'); - expect(steps[1].nextPreparationId, isNull); - }, - ); - - test('changed custom preparation preserves step IDs', () async { - final updateScheduleUseCase = SpyUpdateScheduleUseCase(); - final updatePreparationUseCase = SpyUpdatePreparationByScheduleIdUseCase(); - final useCase = UpdateScheduleFormSubmissionUseCase( - updateScheduleUseCase, - updatePreparationUseCase, - ); - const preparation = PreparationEntity( - preparationStepList: [ - PreparationStepEntity( - id: 'custom-step-1', - preparationName: 'Makeup', - preparationTime: Duration(minutes: 20), - nextPreparationId: 'custom-step-2', - ), - PreparationStepEntity( - id: 'custom-step-2', - preparationName: 'Bathroom', - preparationTime: Duration(minutes: 5), - ), - ], - ); - - await useCase( + test('update preserves committed result and retries delivery only', () async { + final workflow = Workflow(); + final useCase = UpdateScheduleFormSubmissionUseCase(workflow); + final receipt = await useCase( ScheduleFormSubmission( - schedule: schedule(isChanged: true), - preparation: preparation, + schedule: schedule(), + preparation: prep(), preparationChanged: true, - originalPreparationMode: SchedulePreparationMode.custom, ), ); - - final updated = - updatePreparationUseCase.updatedPreparations.single.preparation; - expect(updated.preparationStepList.map((step) => step.id), [ - 'custom-step-1', - 'custom-step-2', - ]); - expect(updated.preparationStepList[0].nextPreparationId, 'custom-step-2'); - expect(updated.preparationStepList[1].nextPreparationId, isNull); + expect(workflow.editing, true); + expect(receipt.deliveryPending, isTrue); + expect((await useCase.retryDelivery(receipt)).deliveryPending, isFalse); + expect(workflow.saves, 1); + expect(workflow.retries, 1); }); } diff --git a/test/fixtures/database/README.md b/test/fixtures/database/README.md new file mode 100644 index 00000000..685bc904 --- /dev/null +++ b/test/fixtures/database/README.md @@ -0,0 +1,12 @@ +# Historical SQLite fixtures for A08 + +`schema_v2.sql` was executed and emitted from actual Drift AppDatabase sources and matching generated artifacts at commit `4529d9f84e911b1ee2fbb42af09698a0c27cc9f2`, copied to `/tmp/a08-schema-baseline`. The extraction test opened `NativeDatabase.memory()`, queried `SELECT sql FROM sqlite_master WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%' ORDER BY type DESC,name`, joined DDL with semicolons and appended `PRAGMA user_version=2`. Command: `/Users/ejunpark/Library/flutter/bin/flutter test test/dump_test.dart` in the isolated temporary source copy; execution log `/tmp/a08-historical-dump.log`. + +`schema_v1.sql` was **reconstructed from that frozen v2 DDL**, removing the four recurring tables and five schedule columns introduced by commit `f0877d1df9aa62528f360f5ee1d3bbb095e2f691`, and setting `user_version=1`. It was checked against the source definitions at `ccc6272bde15937a87dd577157b3330593718759` with `git diff ccc6272bde15937a87dd577157b3330593718759 4529d9f84e911b1ee2fbb42af09698a0c27cc9f2 -- lib/data/tables lib/core/database/database.dart`. Other historical table definitions are unchanged. It was not emitted by running a historical v1 binary. + +The migration tests populate these frozen schemas using sqlite3, close them, then open file databases through the current AppDatabase migration path. They verify v1/v2 migration, rollback, identity and protected data preservation, and future-version refusal. This is native SQLite testing, not a historical released app or device SQLCipher migration claim; D04 owns that device verification. Fixtures never derive from current schema-3 table models. + +SHA-256: + +- `schema_v1.sql`: `782cd3a3cfdca56c67f896a7fbdfa9791a349f77b2b65e21c7f0c0f2ce4a121d` +- `schema_v2.sql`: `a7ec3c57f1c86a58ef45c3417abe187fa4e44a0be87e3861a7182cb3ab02d7c5` diff --git a/test/fixtures/database/schema_v1.sql b/test/fixtures/database/schema_v1.sql new file mode 100644 index 00000000..cad067c1 --- /dev/null +++ b/test/fixtures/database/schema_v1.sql @@ -0,0 +1,15 @@ +CREATE TABLE "places" ("id" TEXT NOT NULL, "place_name" TEXT NOT NULL, PRIMARY KEY ("id")); +CREATE TABLE "preparation_schedules" ("id" TEXT NOT NULL, "schedule_id" TEXT NOT NULL REFERENCES schedules (id), "preparation_name" TEXT NOT NULL, "preparation_time" INTEGER NOT NULL, "next_preparation_id" TEXT NULL REFERENCES preparation_schedules (id), PRIMARY KEY ("id")); +CREATE TABLE "preparation_template_steps" ("id" TEXT NOT NULL, "template_id" TEXT NOT NULL REFERENCES preparation_templates (id), "preparation_name" TEXT NOT NULL, "preparation_time" INTEGER NOT NULL, "position" INTEGER NOT NULL, PRIMARY KEY ("id")); +CREATE TABLE "preparation_templates" ("id" TEXT NOT NULL, "template_name" TEXT NOT NULL, "created_at" INTEGER NOT NULL, "updated_at" INTEGER NOT NULL, PRIMARY KEY ("id")); +CREATE TABLE "preparation_users" ("id" TEXT NOT NULL, "user_id" TEXT NOT NULL REFERENCES users (id), "preparation_name" TEXT NOT NULL, "preparation_time" INTEGER NOT NULL, "next_preparation_id" TEXT NULL REFERENCES preparation_users (id), PRIMARY KEY ("id")); +CREATE TABLE "schedules" ("id" TEXT NOT NULL, "place_id" TEXT NOT NULL REFERENCES places (id), "schedule_name" TEXT NOT NULL, "time_zone_id" TEXT NOT NULL DEFAULT 'UTC', "occurrence_offset_seconds" INTEGER NULL, "schedule_time" TEXT NOT NULL, "move_time" INTEGER NOT NULL, "is_changed" INTEGER NOT NULL DEFAULT 0 CHECK ("is_changed" IN (0, 1)), "is_started" INTEGER NOT NULL DEFAULT 0 CHECK ("is_started" IN (0, 1)), "schedule_spare_time" INTEGER NULL, "schedule_note" TEXT NULL, "lateness_time" INTEGER NOT NULL DEFAULT -1, "done_status" TEXT NOT NULL DEFAULT 'notEnded', "started_at" INTEGER NULL, "finished_at" INTEGER NULL, "preparation_mode" TEXT NULL, "preparation_template_id" TEXT NULL, "preparation_template_name" TEXT NULL, "preparation_template_deleted" INTEGER NOT NULL DEFAULT 0 CHECK ("preparation_template_deleted" IN (0, 1)), "preparation_frozen" INTEGER NOT NULL DEFAULT 0 CHECK ("preparation_frozen" IN (0, 1)), "score_contribution_recorded" INTEGER NOT NULL DEFAULT 0 CHECK ("score_contribution_recorded" IN (0, 1)), PRIMARY KEY ("id")); +CREATE TABLE "users" ("id" TEXT NOT NULL, "spare_time" INTEGER NOT NULL, "note" TEXT NOT NULL, "is_onboarding_completed" INTEGER NOT NULL DEFAULT 0 CHECK ("is_onboarding_completed" IN (0, 1)), "eligible_outcome_count" INTEGER NOT NULL DEFAULT 0, "on_time_outcome_count" INTEGER NOT NULL DEFAULT 0, "alarms_enabled" INTEGER NOT NULL DEFAULT 1 CHECK ("alarms_enabled" IN (0, 1)), "alarm_offset_minutes" INTEGER NOT NULL DEFAULT 0, "detailed_notification_content" INTEGER NOT NULL DEFAULT 0 CHECK ("detailed_notification_content" IN (0, 1)), "data_revision" INTEGER NOT NULL DEFAULT 0, "last_exported_revision" INTEGER NULL, "last_exported_at" INTEGER NULL, "first_durable_data_at" INTEGER NULL, "last_durable_data_at" INTEGER NULL, PRIMARY KEY ("id")); +CREATE INDEX preparation_schedules_next_preparation_id_idx ON preparation_schedules (next_preparation_id); +CREATE INDEX preparation_schedules_schedule_id_idx ON preparation_schedules (schedule_id); +CREATE INDEX preparation_template_steps_template_id_idx ON preparation_template_steps (template_id); +CREATE INDEX preparation_users_next_preparation_id_idx ON preparation_users (next_preparation_id); +CREATE INDEX preparation_users_user_id_idx ON preparation_users (user_id); +CREATE INDEX schedules_place_id_idx ON schedules (place_id); +CREATE INDEX schedules_schedule_time_idx ON schedules (schedule_time); +PRAGMA user_version=1; diff --git a/test/fixtures/database/schema_v2.sql b/test/fixtures/database/schema_v2.sql new file mode 100644 index 00000000..84ae0330 --- /dev/null +++ b/test/fixtures/database/schema_v2.sql @@ -0,0 +1,19 @@ +CREATE TABLE "places" ("id" TEXT NOT NULL, "place_name" TEXT NOT NULL, PRIMARY KEY ("id")); +CREATE TABLE "preparation_definition_steps" ("id" TEXT NOT NULL, "definition_id" TEXT NOT NULL REFERENCES preparation_definitions (id), "name" TEXT NOT NULL, "minutes" INTEGER NOT NULL CHECK (minutes >= 0), "position" INTEGER NOT NULL CHECK (position >= 0), PRIMARY KEY ("id"), UNIQUE ("definition_id", "position")); +CREATE TABLE "preparation_definitions" ("id" TEXT NOT NULL, "owner_id" TEXT NOT NULL, "scope" TEXT NOT NULL, "name" TEXT NOT NULL, "created_at" INTEGER NOT NULL, PRIMARY KEY ("id")); +CREATE TABLE "preparation_schedules" ("id" TEXT NOT NULL, "schedule_id" TEXT NOT NULL REFERENCES schedules (id), "preparation_name" TEXT NOT NULL, "preparation_time" INTEGER NOT NULL, "next_preparation_id" TEXT NULL REFERENCES preparation_schedules (id), PRIMARY KEY ("id")); +CREATE TABLE "preparation_template_steps" ("id" TEXT NOT NULL, "template_id" TEXT NOT NULL REFERENCES preparation_templates (id), "preparation_name" TEXT NOT NULL, "preparation_time" INTEGER NOT NULL, "position" INTEGER NOT NULL, PRIMARY KEY ("id")); +CREATE TABLE "preparation_templates" ("id" TEXT NOT NULL, "template_name" TEXT NOT NULL, "created_at" INTEGER NOT NULL, "updated_at" INTEGER NOT NULL, PRIMARY KEY ("id")); +CREATE TABLE "preparation_users" ("id" TEXT NOT NULL, "user_id" TEXT NOT NULL REFERENCES users (id), "preparation_name" TEXT NOT NULL, "preparation_time" INTEGER NOT NULL, "next_preparation_id" TEXT NULL REFERENCES preparation_users (id), PRIMARY KEY ("id")); +CREATE TABLE "recurring_schedule_exclusions" ("segment_id" TEXT NOT NULL REFERENCES recurring_schedule_segments (id), "slot_key" TEXT NOT NULL, "ordinal" INTEGER NOT NULL, PRIMARY KEY ("segment_id", "slot_key")); +CREATE TABLE "recurring_schedule_segments" ("id" TEXT NOT NULL, "series_id" TEXT NOT NULL, "rule_json" TEXT NOT NULL, "schedule_json" TEXT NOT NULL, "preparation_id" TEXT NOT NULL REFERENCES preparation_definitions (id), "from_slot" TEXT NOT NULL, "before_slot" TEXT NULL, "created_at" INTEGER NOT NULL, "preparation_not_before" INTEGER NULL, PRIMARY KEY ("id")); +CREATE TABLE "schedules" ("id" TEXT NOT NULL, "place_id" TEXT NOT NULL REFERENCES places (id), "schedule_name" TEXT NOT NULL, "time_zone_id" TEXT NOT NULL DEFAULT 'UTC', "occurrence_offset_seconds" INTEGER NULL, "schedule_time" TEXT NOT NULL, "move_time" INTEGER NOT NULL, "is_changed" INTEGER NOT NULL DEFAULT 0 CHECK ("is_changed" IN (0, 1)), "is_started" INTEGER NOT NULL DEFAULT 0 CHECK ("is_started" IN (0, 1)), "schedule_spare_time" INTEGER NULL, "schedule_note" TEXT NULL, "lateness_time" INTEGER NOT NULL DEFAULT -1, "done_status" TEXT NOT NULL DEFAULT 'notEnded', "started_at" INTEGER NULL, "finished_at" INTEGER NULL, "preparation_mode" TEXT NULL, "preparation_template_id" TEXT NULL, "preparation_template_name" TEXT NULL, "preparation_template_deleted" INTEGER NOT NULL DEFAULT 0 CHECK ("preparation_template_deleted" IN (0, 1)), "preparation_frozen" INTEGER NOT NULL DEFAULT 0 CHECK ("preparation_frozen" IN (0, 1)), "score_contribution_recorded" INTEGER NOT NULL DEFAULT 0 CHECK ("score_contribution_recorded" IN (0, 1)), "recurring_segment_id" TEXT NULL, "recurring_slot_key" TEXT NULL, "recurring_ordinal" INTEGER NULL, "recurring_overrides" TEXT NOT NULL DEFAULT '', "preparation_definition_id" TEXT NULL, PRIMARY KEY ("id")); +CREATE TABLE "users" ("id" TEXT NOT NULL, "spare_time" INTEGER NOT NULL, "note" TEXT NOT NULL, "is_onboarding_completed" INTEGER NOT NULL DEFAULT 0 CHECK ("is_onboarding_completed" IN (0, 1)), "eligible_outcome_count" INTEGER NOT NULL DEFAULT 0, "on_time_outcome_count" INTEGER NOT NULL DEFAULT 0, "alarms_enabled" INTEGER NOT NULL DEFAULT 1 CHECK ("alarms_enabled" IN (0, 1)), "alarm_offset_minutes" INTEGER NOT NULL DEFAULT 0, "detailed_notification_content" INTEGER NOT NULL DEFAULT 0 CHECK ("detailed_notification_content" IN (0, 1)), "data_revision" INTEGER NOT NULL DEFAULT 0, "last_exported_revision" INTEGER NULL, "last_exported_at" INTEGER NULL, "first_durable_data_at" INTEGER NULL, "last_durable_data_at" INTEGER NULL, PRIMARY KEY ("id")); +CREATE INDEX preparation_schedules_next_preparation_id_idx ON preparation_schedules (next_preparation_id); +CREATE INDEX preparation_schedules_schedule_id_idx ON preparation_schedules (schedule_id); +CREATE INDEX preparation_template_steps_template_id_idx ON preparation_template_steps (template_id); +CREATE INDEX preparation_users_next_preparation_id_idx ON preparation_users (next_preparation_id); +CREATE INDEX preparation_users_user_id_idx ON preparation_users (user_id); +CREATE INDEX schedules_place_id_idx ON schedules (place_id); +CREATE INDEX schedules_schedule_time_idx ON schedules (schedule_time); +PRAGMA user_version=2; diff --git a/test/presentation/recurring/recurring_form_integration_test.dart b/test/presentation/recurring/recurring_form_integration_test.dart index 8b850c8d..b49d7fd2 100644 --- a/test/presentation/recurring/recurring_form_integration_test.dart +++ b/test/presentation/recurring/recurring_form_integration_test.dart @@ -1,4 +1,10 @@ -import '../../helpers/noop_alarm_reconciliation.dart'; +import 'package:on_time_front/presentation/recurring/recurrence_components.dart'; +import 'package:on_time_front/presentation/shared/components/step_progress.dart'; +import 'dart:io'; +import 'dart:ui' as ui; +import 'package:flutter/rendering.dart'; +import 'package:on_time_front/data/repositories/schedule_aggregate_repository_impl.dart'; +import 'package:on_time_front/domain/use-cases/schedule_save_workflow.dart'; import '../../helpers/refresh_capture.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -27,9 +33,7 @@ import 'package:on_time_front/domain/recurrence/recurrence_rule.dart'; import 'package:on_time_front/domain/recurrence/recurring_schedule.dart'; import 'package:on_time_front/domain/repositories/timed_preparation_repository.dart'; import 'package:on_time_front/domain/repositories/user_repository.dart'; -import 'package:on_time_front/domain/use-cases/create_custom_preparation_use_case.dart'; import 'package:on_time_front/domain/use-cases/create_schedule_form_submission_use_case.dart'; -import 'package:on_time_front/domain/use-cases/create_schedule_with_place_use_case.dart'; import 'package:on_time_front/domain/use-cases/get_default_preparation_use_case.dart'; import 'package:on_time_front/domain/use-cases/get_preparation_by_schedule_id_use_case.dart'; import 'package:on_time_front/domain/use-cases/get_schedule_by_id_use_case.dart'; @@ -37,9 +41,7 @@ import 'package:on_time_front/domain/use-cases/load_preparation_by_schedule_id_u import 'package:on_time_front/domain/use-cases/load_schedule_form_draft_use_case.dart'; import 'package:on_time_front/domain/use-cases/recurring_schedules_use_case.dart'; import 'package:on_time_front/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart'; -import 'package:on_time_front/domain/use-cases/update_preparation_by_schedule_id_use_case.dart'; import 'package:on_time_front/domain/use-cases/update_schedule_form_submission_use_case.dart'; -import 'package:on_time_front/domain/use-cases/update_schedule_use_case.dart'; import 'package:on_time_front/presentation/schedule_create/bloc/schedule_form_bloc.dart'; void main() { @@ -131,6 +133,12 @@ void main() { ); alarms = _AlarmEffects(); final useCase = RecurringSchedulesUseCase(recurring, schedules, alarms); + final aggregate = ScheduleAggregateRepositoryImpl( + db, + recurring, + now: () => now, + ); + final workflow = ScheduleSaveWorkflow(aggregate, alarms); var nextId = 0; bloc = ScheduleFormBloc( LoadScheduleFormDraftUseCase.withOverrides( @@ -138,23 +146,14 @@ void main() { GetPreparationByScheduleIdUseCase(preparations), GetDefaultPreparationUseCase(preparations), GetScheduleByIdUseCase(schedules), + aggregate: aggregate, now: () => now, newId: () => 'new-${nextId++}', timeZoneId: () async => 'UTC', ), - CreateScheduleFormSubmissionUseCase( - CreateScheduleWithPlaceUseCase(schedules, alarms), - CreateCustomPreparationUseCase(preparations, NoopAlarmReconciliation()), - recurringSchedules: useCase, - ), - UpdateScheduleFormSubmissionUseCase( - UpdateScheduleUseCase(schedules, alarms), - UpdatePreparationByScheduleIdUseCase( - preparations, - NoopAlarmReconciliation(), - ), - recurringSchedules: useCase, - ), + CreateScheduleFormSubmissionUseCase(workflow), + UpdateScheduleFormSubmissionUseCase(workflow), + aggregate: aggregate, recurringSchedules: useCase, ); }); @@ -411,6 +410,293 @@ void main() { expect(alarms.operations, [ScheduleMutationAlarmOperation.created]); }, ); + for (final language in ['ko', 'en']) { + testWidgets('ordinary save rollback and delivery-only retry $language', ( + tester, + ) async { + tester.view.physicalSize = const Size(430, 932); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + await tester.runAsync(draft); + await tester.runAsync( + () => event( + const ScheduleFormRecurringChanged(null), + (s) => s.recurrenceRule == null, + ), + ); + final byDate = GetSchedulesByDateUseCase(schedules); + getIt.registerFactoryParam( + (form, _) => ScheduleDateTimeCubit( + form, + LoadAdjacentScheduleWithPreparationUseCase( + LoadSchedulesByDateUseCase(schedules), + byDate, + LoadPreparationByScheduleIdUseCase(preparations), + ), + GetAdjacentSchedulesWithPreparationUseCase( + byDate, + GetPreparationByScheduleIdUseCase(preparations), + ), + ), + ); + addTearDown(getIt.reset); + bool? saved; + await tester.pumpWidget( + MaterialApp( + theme: themeData, + locale: Locale(language), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + body: Builder( + builder: (context) => TextButton( + onPressed: () async { + saved = await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + body: BlocProvider.value( + value: bloc, + child: ScheduleMultiPageForm( + onSaved: () => + bloc.add(const ScheduleFormCreated()), + ), + ), + ), + ), + ); + }, + child: const Text('open'), + ), + ), + ), + ), + ); + await tester.tap(find.text('open')); + await tester.pumpAndSettle(); + for (var page = 0; page < 3; page++) { + for ( + var attempt = 0; + attempt < 30 && + (tester + .widget(find.byType(ScreenActions)) + .onAction == + null || + !bloc.state.isValid); + attempt++ + ) { + await tester.runAsync(() => db.customSelect('SELECT 1').get()); + await tester.pump(); + } + expect( + bloc.state.isValid, + isTrue, + reason: 'Page $page must be valid before tapping Next', + ); + await tester.pump(); + expect( + tester.widget(find.byType(ScreenActions)).onAction, + isNotNull, + ); + await tester.tap(find.text(language == 'ko' ? '다음' : 'Next')); + await tester.pump(const Duration(milliseconds: 500)); + await tester.pumpAndSettle(); + expect( + tester.widget(find.byType(StepProgress)).currentStep, + page + 1, + reason: 'Next should advance actual form page $page', + ); + } + final before = await tester.runAsync( + () async => (await db.select(db.users).getSingle()).dataRevision, + ); + await tester.runAsync( + () => db.customStatement( + "CREATE TRIGGER fail_save BEFORE UPDATE OF data_revision ON users BEGIN SELECT RAISE(ABORT,'injected save fault'); END", + ), + ); + tester.platformDispatcher.textScaleFactorTestValue = 2; + addTearDown(tester.platformDispatcher.clearTextScaleFactorTestValue); + await tester.pump(); + final failed = bloc.stream.firstWhere( + (s) => s.submissionStatus == ScheduleFormSubmissionStatus.failure, + ); + await tester.tap(find.text(language == 'ko' ? '저장' : 'Save')); + await tester.runAsync(() => failed); + await tester.pumpAndSettle(); + expect( + await tester.runAsync(() => db.select(db.schedules).get()), + isEmpty, + ); + expect(bloc.state.scheduleName, '출근'); + expect(saved, isNull); + await captureA08(tester, '$language-rollback'); + await tester.runAsync(() => db.customStatement('DROP TRIGGER fail_save')); + alarms.deliveryComplete = false; + final pending = bloc.stream.firstWhere( + (s) => + s.submissionStatus == ScheduleFormSubmissionStatus.deliveryPending, + ); + await tester.tap( + find.text(language == 'ko' ? '다시 저장하기' : 'Retry saving'), + ); + await tester.runAsync(() => pending); + await tester.pumpAndSettle(); + expect( + await tester.runAsync(() => db.select(db.schedules).get()), + hasLength(1), + ); + expect( + await tester.runAsync( + () async => (await db.select(db.users).getSingle()).dataRevision, + ), + before! + 1, + ); + expect(saved, isNull); + await captureA08(tester, '$language-delivery-pending'); + alarms.deliveryComplete = true; + final done = bloc.stream.firstWhere( + (s) => s.submissionStatus == ScheduleFormSubmissionStatus.success, + ); + final l = AppLocalizations.of(tester.element(find.byType(AlertDialog)))!; + await tester.tap(find.text(l.dataRetry)); + await tester.runAsync(() => done); + await tester.pumpAndSettle(); + expect(saved, isTrue); + expect( + await tester.runAsync(() => db.select(db.schedules).get()), + hasLength(1), + ); + expect( + await tester.runAsync( + () async => (await db.select(db.users).getSingle()).dataRevision, + ), + before + 1, + ); + expect(tester.takeException(), isNull); + await tester.pumpWidget(const SizedBox.shrink()); + }); + } + + for (final modal in ['review', 'receipt']) { + testWidgets('late $modal answer cannot submit or close replacement draft', ( + tester, + ) async { + tester.view.physicalSize = const Size(430, 932); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + await tester.runAsync(draft); + final byDate = GetSchedulesByDateUseCase(schedules); + getIt.registerFactoryParam( + (form, _) => ScheduleDateTimeCubit( + form, + LoadAdjacentScheduleWithPreparationUseCase( + LoadSchedulesByDateUseCase(schedules), + byDate, + LoadPreparationByScheduleIdUseCase(preparations), + ), + GetAdjacentSchedulesWithPreparationUseCase( + byDate, + GetPreparationByScheduleIdUseCase(preparations), + ), + ), + ); + addTearDown(getIt.reset); + await tester.pumpWidget( + MaterialApp( + theme: themeData, + locale: const Locale('ko'), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + body: BlocProvider.value( + value: bloc, + child: ScheduleMultiPageForm( + onSaved: () => bloc.add(const ScheduleFormCreated()), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + if (modal == 'receipt') { + await tester.runAsync( + () => event( + const ScheduleFormRecurringChanged(null), + (s) => s.recurrenceRule == null, + ), + ); + alarms.deliveryComplete = false; + } + await tester.runAsync( + () => event( + const ScheduleFormCreated(), + (s) => + s.submissionStatus == + (modal == 'review' + ? ScheduleFormSubmissionStatus.review + : ScheduleFormSubmissionStatus.deliveryPending), + ), + ); + await tester.pumpAndSettle(); + final previousOwner = bloc.formOwner; + await tester.runAsync(draft); + await tester.pump(); + final currentId = bloc.state.id; + expect(identical(previousOwner, bloc.formOwner), isFalse); + final label = modal == 'review' + ? '저장하기' + : AppLocalizations.of( + tester.element(find.byType(AlertDialog)), + )!.dataRetry; + await tester.tap(find.text(label)); + await tester.pumpAndSettle(); + await tester.runAsync(() => db.customSelect('SELECT 1').get()); + await tester.pump(); + expect(bloc.state.id, currentId); + expect(bloc.state.submissionStatus, ScheduleFormSubmissionStatus.idle); + expect(bloc.state.saveReceipt, isNull); + expect(find.byType(ScheduleMultiPageForm), findsOneWidget); + expect( + await tester.runAsync(() => db.select(db.schedules).get()), + hasLength(modal == 'review' ? 0 : 1), + ); + await tester.pumpWidget(const SizedBox.shrink()); + }); + } + test( + 'partial receipt from form A cannot replace actual save of new form B', + () async { + await draft(); + await event( + const ScheduleFormRecurringChanged(null), + (s) => s.recurrenceRule == null, + ); + alarms.deliveryComplete = false; + await event( + const ScheduleFormCreated(), + (s) => + s.submissionStatus == ScheduleFormSubmissionStatus.deliveryPending, + ); + final first = bloc.state.id; + expect(bloc.state.saveReceipt, isNotNull); + await draft(); + await event( + const ScheduleFormRecurringChanged(null), + (s) => s.recurrenceRule == null, + ); + expect(bloc.state.id, isNot(first)); + expect(bloc.state.saveReceipt, isNull); + expect(bloc.state.originalSchedule, isNull); + alarms.deliveryComplete = true; + await event( + const ScheduleFormCreated(), + (s) => s.submissionStatus == ScheduleFormSubmissionStatus.success, + ); + expect(await db.select(db.schedules).get(), hasLength(2)); + }, + ); } PreparationEntity _preparation(String name, int minutes) => PreparationEntity( @@ -428,7 +714,14 @@ class _UnusedUser extends Fake implements UserRepository {} class _UnusedTimers extends Fake implements TimedPreparationRepository {} class _AlarmEffects implements ScheduleMutationAlarmEffectsCoordinator { + bool deliveryComplete = true; final operations = []; + @override + Future afterCommit() async { + operations.add(ScheduleMutationAlarmOperation.created); + return deliveryComplete; + } + @override Future call({ required ScheduleMutationAlarmOperation operation, @@ -437,3 +730,19 @@ class _AlarmEffects implements ScheduleMutationAlarmEffectsCoordinator { operations.add(operation); } } + +Future captureA08(WidgetTester tester, String name) async { + const path = String.fromEnvironment('A08_CAPTURE_DIR'); + if (path.isEmpty) return; + await tester.runAsync(() async { + final view = tester.binding.renderViews.first; + final image = await (view.debugLayer! as OffsetLayer).toImage( + Offset.zero & view.size, + pixelRatio: 1, + ); + final bytes = await image.toByteData(format: ui.ImageByteFormat.png); + await Directory(path).create(recursive: true); + await File('$path/$name.png').writeAsBytes(bytes!.buffer.asUint8List()); + image.dispose(); + }); +} diff --git a/test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart b/test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart index e523f06b..5262c5cb 100644 --- a/test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart +++ b/test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:on_time_front/domain/entities/place_entity.dart'; import 'package:on_time_front/domain/entities/preparation_entity.dart'; @@ -46,10 +48,19 @@ class SpyCreateScheduleFormSubmissionUseCase final submissions = []; @override - Future call(ScheduleFormSubmission submission) async { + Future call(ScheduleFormSubmission submission) async { submissions.add(submission); await handler?.call(submission); + return ScheduleSaveReceipt( + scheduleId: submission.schedule.id, + mutationId: submission.mutationId ?? 'test', + generation: 0, + changed: true, + ); } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class SpyUpdateScheduleFormSubmissionUseCase @@ -58,10 +69,19 @@ class SpyUpdateScheduleFormSubmissionUseCase final submissions = []; @override - Future call(ScheduleFormSubmission submission) async { + Future call(ScheduleFormSubmission submission) async { submissions.add(submission); await handler?.call(submission); + return ScheduleSaveReceipt( + scheduleId: submission.schedule.id, + mutationId: submission.mutationId ?? 'test', + generation: 0, + changed: true, + ); } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } void main() { @@ -371,4 +391,60 @@ void main() { expect(bloc.state.isChanged, IsPreparationChanged.unchanged); }); + test( + 'old pending save completion cannot replace a newly loaded form', + () async { + final bloc = buildBloc(); + addTearDown(bloc.close); + await primeEditState(bloc); + final entered = Completer(); + final release = Completer(); + updateScheduleFormSubmissionUseCase.handler = (_) { + entered.complete(); + return release.future; + }; + bloc.add(const ScheduleFormUpdated()); + await entered.future; + final ready = bloc.stream.firstWhere( + (s) => + s.status == ScheduleFormStatus.success && + s.submissionStatus == ScheduleFormSubmissionStatus.idle, + ); + bloc.add(const ScheduleFormCreateRequested()); + await ready; + final newMutation = bloc.state.mutationId; + release.complete(); + await pumpEventQueue(); + expect(bloc.state.mutationId, newMutation); + expect(bloc.state.submissionStatus, ScheduleFormSubmissionStatus.idle); + }, + ); + test( + 'pending save ignores input and repeated submit until actual completion', + () async { + final bloc = buildBloc(); + addTearDown(bloc.close); + await primeEditState(bloc); + final entered = Completer(); + final release = Completer(); + updateScheduleFormSubmissionUseCase.handler = (_) { + entered.complete(); + return release.future; + }; + final name = bloc.state.scheduleName; + bloc.add(const ScheduleFormUpdated()); + await entered.future; + bloc.add( + const ScheduleFormScheduleNameChanged(scheduleName: 'Late input'), + ); + bloc.add(const ScheduleFormReviewDismissed()); + bloc.add(const ScheduleFormUpdated()); + await pumpEventQueue(); + expect(bloc.state.scheduleName, name); + expect(updateScheduleFormSubmissionUseCase.submissions, hasLength(1)); + release.complete(); + await pumpEventQueue(); + expect(bloc.state.submissionStatus, ScheduleFormSubmissionStatus.success); + }, + ); } diff --git a/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart b/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart index 17234f14..8afa5e61 100644 --- a/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart +++ b/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart @@ -1,3 +1,4 @@ +import 'package:on_time_front/domain/entities/schedule_save.dart'; import 'dart:async'; import 'package:flutter/material.dart'; @@ -79,9 +80,18 @@ class SpyCreateScheduleFormSubmissionUseCase Future Function(ScheduleFormSubmission submission)? handler; @override - Future call(ScheduleFormSubmission submission) async { + Future call(ScheduleFormSubmission submission) async { await handler?.call(submission); + return ScheduleSaveReceipt( + scheduleId: submission.schedule.id, + mutationId: submission.mutationId ?? 'test', + generation: 0, + changed: true, + ); } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class SpyUpdateScheduleFormSubmissionUseCase @@ -89,9 +99,18 @@ class SpyUpdateScheduleFormSubmissionUseCase Future Function(ScheduleFormSubmission submission)? handler; @override - Future call(ScheduleFormSubmission submission) async { + Future call(ScheduleFormSubmission submission) async { await handler?.call(submission); + return ScheduleSaveReceipt( + scheduleId: submission.schedule.id, + mutationId: submission.mutationId ?? 'test', + generation: 0, + changed: true, + ); } + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class StubLoadAdjacentScheduleWithPreparationUseCase From ee6c1f8b41fedbafee493791514026eaf669dcb5 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Thu, 24 Sep 2026 13:10:09 +0900 Subject: [PATCH 59/59] docs: preserve aggregate save evidence and localization decisions --- handoff/audit-2026-09-23-progress.md | 5 + plans/audit-2026-09-23/README.md | 9 +- .../a08-final-validation.json | 398 ++++++ .../artifacts/a08/a08-analyze-final.log | 2 + .../artifacts/a08/a08-android-manifest.log | 1 + .../artifacts/a08/a08-codegen-final-v3.log | 22 + .../artifacts/a08/a08-coverage-gate.log | 1 + .../artifacts/a08/a08-diff-check.log | 0 .../artifacts/a08/a08-focused-final.log | 149 +++ .../artifacts/a08/a08-focused-v3.log | 62 + .../a08/a08-full-coverage-before-ordering.log | 657 ++++++++++ .../a08/a08-full-coverage-first-failure.log | 1168 +++++++++++++++++ .../artifacts/a08/a08-full-coverage.log | 1059 +++++++++++++++ .../artifacts/a08/a08-full-fix-focused.log | 65 + .../artifacts/a08/a08-generated-policy.log | 1 + .../artifacts/a08/a08-handoff.md | 36 + .../artifacts/a08/a08-historical-dump.log | 3 + .../artifacts/a08/a08-l10n-freeze.log | 2 + .../artifacts/a08/a08-local-only.log | 1 + .../a08/a08-materialization-final-focused.log | 43 + .../a08/a08-materialization-probe.log | 3 + .../a08-materialization-probe_test.dart.txt | 32 + .../artifacts/a08/a08-negative-proof.json | 13 + .../artifacts/a08/a08-negative-test.dart.txt | 41 + .../artifacts/a08/a08-negative.log | 17 + .../artifacts/a08/a08-ordering-focused.log | 13 + .../artifacts/a08/a08-python-tests.log | 16 + .../artifacts/a08/a08-review-transcript.md | 97 ++ .../artifacts/a08/a08-source-freeze-v1.json | 54 + .../artifacts/a08/a08-source-freeze-v2.json | 63 + .../artifacts/a08/a08-source-freeze-v3.json | 61 + .../artifacts/a08/a08-source-freeze.json | 61 + .../artifacts/a08/en-delivery-pending.png | Bin 0 -> 66168 bytes .../artifacts/a08/en-rollback.png | Bin 0 -> 63907 bytes .../artifacts/a08/ko-delivery-pending.png | Bin 0 -> 52802 bytes .../artifacts/a08/ko-rollback.png | Bin 0 -> 54390 bytes plans/audit-2026-09-23/issues/A08.md | 123 ++ plans/audit-2026-09-23/issues/C01.md | 5 + plans/audit-2026-09-23/issues/U05.md | 197 +++ plans/audit-2026-09-23/state.json | 48 +- 40 files changed, 4506 insertions(+), 22 deletions(-) create mode 100644 plans/audit-2026-09-23/a08-final-validation.json create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-analyze-final.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-android-manifest.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-codegen-final-v3.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-coverage-gate.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-diff-check.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-focused-final.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-focused-v3.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-before-ordering.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-first-failure.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-full-coverage.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-full-fix-focused.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-generated-policy.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-handoff.md create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-historical-dump.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-l10n-freeze.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-local-only.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-materialization-final-focused.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe_test.dart.txt create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-negative-proof.json create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-negative-test.dart.txt create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-negative.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-ordering-focused.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-python-tests.log create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-review-transcript.md create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v1.json create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v2.json create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v3.json create mode 100644 plans/audit-2026-09-23/artifacts/a08/a08-source-freeze.json create mode 100644 plans/audit-2026-09-23/artifacts/a08/en-delivery-pending.png create mode 100644 plans/audit-2026-09-23/artifacts/a08/en-rollback.png create mode 100644 plans/audit-2026-09-23/artifacts/a08/ko-delivery-pending.png create mode 100644 plans/audit-2026-09-23/artifacts/a08/ko-rollback.png create mode 100644 plans/audit-2026-09-23/issues/U05.md diff --git a/handoff/audit-2026-09-23-progress.md b/handoff/audit-2026-09-23-progress.md index 5713a257..17a7537c 100644 --- a/handoff/audit-2026-09-23-progress.md +++ b/handoff/audit-2026-09-23-progress.md @@ -146,3 +146,8 @@ D06#623 전담실제4문답·27개검증·10개AC 게시/readback완료,37/64( D07#624 실제3문답+Drift버전정정·18검증·10AC 게시/readback완료,38/64(미생성26). template restore전용createdAt/updatedAt보존·template-onlyUTCexport·id동률정렬·legacyoffsetless한계·nonzero subsecond사전거부/공유validator·A09/D02/양플랫폼통합검증확정. U04 새전담 /root/grill_u04 착수. A08최종analyzer정리중이고sourcefreeze대기. U04#625 실제3문답/보충·21검증·8AC 게시/readback완료,39/64(미생성25). 결과기간만기존locale-awareformatter재사용,abs/초버림/기존suffix·outcome·점수유지,realheadline음성회귀및KOEN/200%표본;U05새전담 /root/grill_u05 착수. A08첫freeze47개(e7e9cfb8...)의첫full=1008pass/7fail로구freeze를보존후보완중. DAOmetadataequality5개와startwidgetlatch2개를분리확인; legacyDAO가oldrowconcurrencymetadata덮지않도록제외하고AFTERtrigger후sameTXreadback,watch단일SQLsnapshot으로변경해집중63통과. root가watch기존scheduleTimeASC유실도찾아정렬복구/회귀요청, 새freeze/전체재검증대기. 첫full실패를최종성공으로표시금지. + + +## 2026-09-24 A08 구현 커밋·A09 착수 + +A08 source126161dab042365c89983934b1673d860c27d8d3: 49파일/commitblob 일치, 전체1018/88.96%(14113/15864) 제품바이트와 최종 동일. 이후 테스트만 실제60→65 materialization으로 보강하여 focused41/probe1/analyzer 통과. a08-final-validation.json 및 artifacts/a08에33개 원본/정규화 증거, 실패full7건·중단로그·실제 전체 추가문답 보존. #601/600 실제 전체문답/결과 갱신 후 exact read-back 완료, CI/device pending. A09 /root/implement_a09가 제품/Flutter 독점. U05#626 실제3문답·24matrix·11AC 게시/readback,40/64(24생성전). U06 새전담 grill 시작. PR본문 동결 승인 질문 미응답이며 재시도/우회하지 않는다. diff --git a/plans/audit-2026-09-23/README.md b/plans/audit-2026-09-23/README.md index 29676fd0..99b597fc 100644 --- a/plans/audit-2026-09-23/README.md +++ b/plans/audit-2026-09-23/README.md @@ -29,9 +29,9 @@ | D03 | [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 독립 journal·복구/host kill11, main통합 원격882 tests/88.50%·APK 통과,기기 대기 | | A06 | [#598](https://github.com/DevKor-github/OnTime-front/issues/598) | d06474d4 실제 타이머 전환 구현,47경계 및 원격929전체·88.79%·APK 통과,기기 대기 | | A07 | [#599](https://github.com/DevKor-github/OnTime-front/issues/599) | d73c7863 원자적 시작·partial retry 구현,원격944 tests/88.70%·APK 및 KOEN200% 검증,기기 대기 | -| C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 4529d9f8 백업/reset workflow 구현,원격959 tests·88.72%·APK 및 KOEN200% 통과,A08/A09/D02 통합 대기 | -| A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 구현 중: 원자 저장/조회·schema3 최소 receipt·충돌/멱등, 실제 partial-write 결함 재현 | -| A09 | [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | 실제4문답·staging/runtime 세대·복원 후 정리 기준 확정 | +| C01 | [#600](https://github.com/DevKor-github/OnTime-front/issues/600) | 4529d9f8 workflow·A08 aggregate 통합,원격959 및 로컬1018 검증,A09/D02 대기 | +| A08 | [#601](https://github.com/DevKor-github/OnTime-front/issues/601) | 126161da 원자 저장·schema3 receipt,전체1018/88.96%·최종집중41/KOEN200% 검증,CI·기기 대기 | +| A09 | [#602](https://github.com/DevKor-github/OnTime-front/issues/602) | 구현 중: 암호화 staging/runtime 세대·복원 후 정리·durable 기록 보존 | | D01 | [#603](https://github.com/DevKor-github/OnTime-front/issues/603) | 실제4문답·DB 비의존 시작 복구·키/파일 보존 기준 확정 | | D02 | [#604](https://github.com/DevKor-github/OnTime-front/issues/604) | 실제4문답·손상 DB 후보 검증/활성 쌍 교체·중단 복구 기준 확정 | | D04 | [#605](https://github.com/DevKor-github/OnTime-front/issues/605) | 실제4문답·migration 원자성/역사 schema/SQLCipher 확인·실패 보존 기준 확정 | @@ -55,8 +55,9 @@ | D06 | [#623](https://github.com/DevKor-github/OnTime-front/issues/623) | 실제 4문답·최초 미백업 age/동시 export/7일 숨김·27개 검증 기준 확정 | | D07 | [#624](https://github.com/DevKor-github/OnTime-front/issues/624) | 실제 3문답·템플릿 두 시각/UTC·legacy/안정 정렬·18개 검증 기준 확정 | | U04 | [#625](https://github.com/DevKor-github/OnTime-front/issues/625) | 실제 3문답·1시간/2시간 표시·기존 현지화 formatter 재사용·21개 검증 기준 확정 | +| U05 | [#626](https://github.com/DevKor-github/OnTime-front/issues/626) | KO/EN 문구·locale 해석·시각 표시·native 활성 경로의 일관성과 작업 중 언어 변경을 보장한다. | -현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **39/64개**를 게시하고 원격 본문 동일성을 확인했으며 **25개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했다. C01은4529d9f8로 구현해 최종 원격959 tests·88.72%·APK를 통과했고 A08 구현 중이다. A08의 현재 부분 검증은 집중52개와 역사 migration5개이며 최종 전체 검증은 남아 있다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. +현재 Draft PR은 [#585](https://github.com/DevKor-github/OnTime-front/pull/585)다. 상세 이슈 **40/64개**를 게시하고 원격 본문 동일성을 확인했으며 **24개는 생성 전**이다. A12 최종 원격707 tests·APK, A13 후속 수정 최종 원격740 tests·coverage87.20%·APK가 통과했다. A14는282db21f로 전달했다. A15는ecd04293로 커밋했고 최종 로컬795 tests/analyze 및 실제 ownership/lifecycle/mutation 회귀를 통과했다. 최종 원격795 tests/analyze·87.66% 및 APK를 확인했고 D03 구현을 시작했다. D05와 A10까지 실제 문답과 백업·복구·시간대 검증 설계가 확정됐으며 D03은 독립 리뷰 보완 및 host kill11 후 최신 main 통합 원격882 tests/88.50%·APK를 통과했고 기기 검증이 남았다. A06은d06474d4로 구현해 최종929 tests/88.74%·신규47경계를 통과했고 원격929 tests/88.79%·APK도 통과했다. A07은d73c7863로 구현해 실제 DB 회귀/전체944 tests·88.67%·KOEN200% 및 원격944/88.70%·APK를 통과했다. C01은4529d9f8로 구현해 최종 원격959 tests·88.72%·APK를 통과했고 A08은126161da에 구현해 최종 제품 전체1018 tests/88.96%, 최종 테스트 보강41개/analyzer 및KOEN200%를 검증했고 원격CI·기기검증은 대기 중이다. A09 구현을 이어간다. U08 홈 overflow 선행 수정은 포함됐지만 전체 매트릭스와 기기 검증은 남아 있다. 자동 검증·이슈 개설·병합·실제 제품 완료를 구별하며 미실행 수용 조건이 있는 이슈는 열어 둔다. ## 실행 순서 diff --git a/plans/audit-2026-09-23/a08-final-validation.json b/plans/audit-2026-09-23/a08-final-validation.json new file mode 100644 index 00000000..4519146c --- /dev/null +++ b/plans/audit-2026-09-23/a08-final-validation.json @@ -0,0 +1,398 @@ +{ + "issue": "A08 #601", + "status": "implemented_and_locally_validated", + "created_at": "2026-09-24T03:58:34.293942+00:00", + "implementation_base": "4529d9f84e911b1ee2fbb42af09698a0c27cc9f2", + "source_manifest": "/tmp/a08-source-freeze.json", + "source_path_count": 49, + "source_hashes_match": true, + "product_identical_to_successful_full_run": true, + "full_run_test_only_delta": [ + "test/data/repositories/schedule_aggregate_repository_test.dart" + ], + "full_run_delta_explanation": "The count=3 re-materialization no-op regression was strengthened to count=65. Actual create inserts 60; subsequent materialization inserts 5 more while root version, mutation version, durable revision and same-intent receipt remain unchanged. Root explicitly requested permanent regression plus focused rerun, without redundant full rerun. Product sources are byte-identical to the full1018 run.", + "coverage": { + "percent": 88.96, + "covered_lines": 14113, + "total_lines": 15864, + "scope": "repository app-owned check_coverage.dart; successful v3 full run" + }, + "commands": [ + { + "command": "flutter analyze --no-pub", + "exit_code": 0, + "result": "No issues found", + "log": "/tmp/a08-analyze-final.log", + "source": "final freeze" + }, + { + "command": "flutter test --no-pub --coverage --reporter expanded", + "exit_code": 0, + "passed": 1018, + "failed": 0, + "log": "/tmp/a08-full-coverage.log", + "source_manifest": "/tmp/a08-source-freeze-v3.json", + "duration_reported_seconds": 55 + }, + { + "command": "flutter test --no-pub test/data/repositories/schedule_aggregate_repository_test.dart --reporter expanded", + "exit_code": 0, + "passed": 41, + "failed": 0, + "log": "/tmp/a08-materialization-final-focused.log", + "source": "final freeze; test-only strengthening after full run" + }, + { + "command": "flutter test --no-pub /tmp/a08-materialization-probe_test.dart --reporter expanded", + "exit_code": 0, + "passed": 1, + "log": "/tmp/a08-materialization-probe.log" + }, + { + "command": "dart run tool/check_coverage.dart --min=80", + "exit_code": 0, + "log": "/tmp/a08-coverage-gate.log" + }, + { + "command": "dart run tool/check_generated_dart_policy.dart", + "exit_code": 0, + "log": "/tmp/a08-generated-policy.log" + }, + { + "command": "dart run tool/check_local_only_boundary.dart", + "exit_code": 0, + "log": "/tmp/a08-local-only.log" + }, + { + "command": "python3 -B -m unittest discover -s test/tool -p 'test_*.py' -v", + "exit_code": 0, + "passed": 11, + "log": "/tmp/a08-python-tests.log" + }, + { + "command": "python3 tool/check_android_notification_manifest.py android/app/src/main/AndroidManifest.xml", + "exit_code": 0, + "log": "/tmp/a08-android-manifest.log" + }, + { + "command": "git diff --check", + "exit_code": 0, + "log": "/tmp/a08-diff-check.log" + }, + { + "command": "dart run build_runner build --delete-conflicting-outputs", + "exit_code": 0, + "log": "/tmp/a08-codegen-final-v3.log", + "note": "Ignored generated outputs remain untracked; no generated Dart staged." + }, + { + "command": "flutter gen-l10n", + "exit_code": 0, + "log": "/tmp/a08-l10n-freeze.log" + } + ], + "focused_evidence": [ + { + "log": "/tmp/a08-focused-final.log", + "passed": 112, + "boundary": "Before final DAO metadata/readback/watch ordering fixes" + }, + { + "log": "/tmp/a08-focused-v3.log", + "passed": 60, + "boundary": "Final product freeze; changed DAO/start UI/aggregate/snapshot tests" + } + ], + "historical_migration": { + "v2_baseline": "4529d9f84e911b1ee2fbb42af09698a0c27cc9f2", + "v2_method": "Actual baseline Drift AppDatabase NativeDatabase memory sqlite_master DDL dump in isolated temporary source copy", + "v1_source": "ccc6272bde15937a87dd577157b3330593718759", + "v1_method": "Reconstruction of frozen v2 DDL using verified historical difference, not v1 binary execution", + "fixtures": { + "test/fixtures/database/schema_v1.sql": "782cd3a3cfdca56c67f896a7fbdfa9791a349f77b2b65e21c7f0c0f2ce4a121d", + "test/fixtures/database/schema_v2.sql": "a7ec3c57f1c86a58ef45c3417abe187fa4e44a0be87e3861a7182cb3ab02d7c5" + }, + "boundary": "Actual native SQLite file open/migration/rollback, not mobile SQLCipher or a released historical binary" + }, + "ui": { + "path": "/tmp/a08-ui", + "languages": [ + "ko", + "en" + ], + "size": [ + 430, + 932 + ], + "text_scale": 2, + "capture_type": "synthetic Flutter widget raster", + "images_inspected": 4, + "behavior": "Actual DB revision fault rollback, preserved draft, successful one-commit retry, notification-only retry, new-form modal ownership", + "limitations": "Existing English background step-label wrapping/title truncation belongs to U08; no entire-form accessibility or native notification claim. Final product UI source and UI test hashes equal capture-run versions." + }, + "previous_validation": { + "first_full": { + "passed": 1008, + "failed": 7, + "log": "/tmp/a08-full-coverage-first-failure.log", + "resolved": "5 old raw-row metadata assertions; 2 unchanged A07 start widget timeouts from extra async watch read transaction" + }, + "interrupted_full": { + "log": "/tmp/a08-full-coverage-before-ordering.log", + "reason": "Stopped only the owned validation PID for root-reviewed chronological watch ordering fix", + "passing_evidence": false + } + }, + "remaining_integration_obligations": [ + "A09/D02 must rotate durable store incarnation for any data replacement, including profile-preserving replacement.", + "C01 verified staging/runtime replacement and damaged-store recovery remain A09/D02 responsibilities.", + "Device SQLCipher migration, actual mobile OS notification/file-provider delivery and external CI/merge/release are not local proof." + ], + "git_actions": "No stage, commit, push, or GitHub mutations by this agent", + "artifacts": { + "plans/audit-2026-09-23/artifacts/a08/a08-source-freeze.json": "53f354317242075bb0c6ce7b02bdad8282e29ea58752fef8f3ee45f970c3a740", + "plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v1.json": "e7e9cfb8fd270bf85034eaaf95b35bf8decae940f859f12abfef642f729e343c", + "plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v2.json": "75f3f3f607495b8e0be3b9ed3a9e7eb4cdf8b5ef053d5fbe3edbb34990985a70", + "plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v3.json": "40c22c1505b5ea38b1434904d26bd1c36a531996ac917234cc428f5528002beb", + "plans/audit-2026-09-23/artifacts/a08/a08-negative-proof.json": "b697d371af0118e9fe899b81d09cfd744fade5b1a47663d1ca8cdb45ef88fb5f", + "plans/audit-2026-09-23/artifacts/a08/a08-negative.log": "8668c4cd448bb0ffe72ee819008eccd85bc6eef5719d2844990c05c6fde939e9", + "plans/audit-2026-09-23/artifacts/a08/a08-negative-test.dart.txt": "692bce0eef10bde945c34f2741fa30f6ec7fa5964e605c638913ce8a85134e60", + "plans/audit-2026-09-23/artifacts/a08/a08-review-transcript.md": "f0017f580c17c2756db8fe06efc49da9b21b84808b36013b1cf2166737513947", + "plans/audit-2026-09-23/artifacts/a08/a08-handoff.md": "7a77842f0bb93f8ee890b58021d3e9cfc9dfece5300c9484d960e8d94527ad15", + "plans/audit-2026-09-23/artifacts/a08/a08-codegen-final-v3.log": "0f16236d9c8e2025d3cc44a0373bd8d31a73db2de9e469b021d7bdf018ff67e4", + "plans/audit-2026-09-23/artifacts/a08/a08-l10n-freeze.log": "99777303f8bb4635eabe383963a4f0617345de959016f82712a51702233027bf", + "plans/audit-2026-09-23/artifacts/a08/a08-focused-final.log": "94f93bde059d45da77958c7c44cd5360e23abbc089ae10483387eaab0306e539", + "plans/audit-2026-09-23/artifacts/a08/a08-full-fix-focused.log": "c5062462e3edbc630aa30b67161c637e982c31cefcaa77515d91c6624ebbfacb", + "plans/audit-2026-09-23/artifacts/a08/a08-focused-v3.log": "c6e445464ece78dcb03fd32876a1793fb5c24972a566cf0328a946a5e0947619", + "plans/audit-2026-09-23/artifacts/a08/a08-ordering-focused.log": "9945f8c06d79a8cbe4ecc62476ab8ef5242dafe0c439030a280fc2d352c7421a", + "plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe.log": "52ddcfe637cb22c6ea0009b09b1d43d74410905215bf21f29b2221bb28b61e99", + "plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe_test.dart.txt": "ef383b108b63fe21db87a919ddf4ada20879791878ef956710a362681f265d17", + "plans/audit-2026-09-23/artifacts/a08/a08-materialization-final-focused.log": "7a120e1964404e4895db3b80014b90cfe6f740975a4e6a9be6008febb2871dd0", + "plans/audit-2026-09-23/artifacts/a08/a08-analyze-final.log": "05ca0c927e73dde40e340806626c09232bd942284df8f27912e73aa97c768d17", + "plans/audit-2026-09-23/artifacts/a08/a08-full-coverage.log": "019597612f9787d7c82f1742e61b3ba8f8df1f6794f32a4bb7265242f78bbe07", + "plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-first-failure.log": "34a7487c17c64d7ad2cbf74544a686148e705db1cb6d03dee0b49ca58b412d68", + "plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-before-ordering.log": "38805b7b01583f30b9ef6409299fe288bbe7fd648edd36a7d34be25836f1b988", + "plans/audit-2026-09-23/artifacts/a08/a08-coverage-gate.log": "197d63302fe72600f85a8cb3f4fba0c94bf875c120cd6a8aea8d21351e720a8b", + "plans/audit-2026-09-23/artifacts/a08/a08-generated-policy.log": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "plans/audit-2026-09-23/artifacts/a08/a08-local-only.log": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "plans/audit-2026-09-23/artifacts/a08/a08-python-tests.log": "bf82059427ffe9b6f0424d5d1d0cce246bd4ae3114fef7fdb2d5aed0bab949f6", + "plans/audit-2026-09-23/artifacts/a08/a08-android-manifest.log": "60ac45f9be8ec739ed709fa43c33c8f51a94665b6bdea633dd65e27529d13c01", + "plans/audit-2026-09-23/artifacts/a08/a08-diff-check.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "plans/audit-2026-09-23/artifacts/a08/a08-historical-dump.log": "ea9b1ee49d5da13cbca47fe908e35c0ab4786083f6771fe0bdf47930e1cf885e", + "plans/audit-2026-09-23/artifacts/a08/en-delivery-pending.png": "15eca6d2860787db4247be58146000b6bb8742ff6179c8d5ee9481b3c4d9a05b", + "plans/audit-2026-09-23/artifacts/a08/en-rollback.png": "04f2bf551f26a6813fbce8573794f0701baaf0ced617b77b8f0d3e83f10d1920", + "plans/audit-2026-09-23/artifacts/a08/ko-delivery-pending.png": "daccffe2454d4c300a8f4925a6a45e8e7b925b09824b3f98ea824a6fedb8c115", + "plans/audit-2026-09-23/artifacts/a08/ko-rollback.png": "3c732763d94fade1caa35d69aaf2e97e995e0d568a28a4e93628d9a5cc1ef976" + }, + "implementation_commit": "126161dab042365c89983934b1673d860c27d8d3", + "source_files": { + "docs/adr/0037-bind-schedule-save-receipts-to-local-aggregate-identities.md": "8bfef8e2fd05f0ad9a7e495475d534d5b6d5a4b0d5be33df77dcf065f3ced964", + "lib/core/backup/recurring_backup_data.dart": "875221d414768e380ec7d6f731f371840c1f39ca8efe8dc88b75949c22caf5b7", + "lib/core/database/database.dart": "c217c5ffcd37825f480c3ff303316ba2db8680d1221138fccffda7d6016f1cef", + "lib/core/database/schedule_aggregate_schema.dart": "6fcc4e2dce5a78d0cb435777782573aac03eaf1152451875267cc9c4d7f9a312", + "lib/data/daos/schedule_dao.dart": "370e4aa68d2159c5e8a24f5b86e704b19758c3365903e6cb1045b5555610e738", + "lib/data/repositories/alarm_repository_impl.dart": "bb32b840e90f68e9073f05022ff1ebac6cace18abb078afc02af62e7990de45e", + "lib/data/repositories/preparation_repository_impl.dart": "bb430d86ec37a7b3df629c44f0fa34dbd51b48102dd723d9d9d78446b56fbc80", + "lib/data/repositories/recurring_schedule_repository_impl.dart": "dbb70fd633b08d5a81619d33b1feb7938bcfffde1825bc0a53fdbe1118bf92fb", + "lib/data/repositories/schedule_aggregate_repository_impl.dart": "11df5dfdc0fb4f0c292db82edc12e789ee0bd9070cd2956f6e8c6ed2383e2e9a", + "lib/data/tables/recurring_schedule_tables.dart": "d1e76b7157a0ffbfef018ccebb28ffd1848d7cf56cebfa94a69651ea6574e3bb", + "lib/data/tables/schedules_table.dart": "33b39eb13aa513e01723da00f4914da0d6cf8d1ed07e16a0070bf9f6a800aa18", + "lib/data/tables/user_table.dart": "b21c5c98f36f7237b9d632d8afe41fff4d665235f36cc8a2e146178c9b67b046", + "lib/domain/entities/schedule_save.dart": "9fd020ed04d81f3fc8f552cf9b9437f27bd6f74159cb038bbc1590909dde3b76", + "lib/domain/repositories/schedule_aggregate_repository.dart": "3755c4135d26ee85bbfae3a19c56395a2e444f6cf6c89cb335d23b3af3df5e61", + "lib/domain/use-cases/create_schedule_form_submission_use_case.dart": "839b370b1ee876612a761848d2fb038c6f83741d520de05c9ca9e4c0ddec72c8", + "lib/domain/use-cases/load_schedule_form_draft_use_case.dart": "0cc6587d76d5fb0194b7bf47cb21169e26bb043fc9a1fd8bd72da8fbbc37ff34", + "lib/domain/use-cases/recurring_schedules_use_case.dart": "9384895177fe325a067f42388b59a704dca3966aa932de84346fb94d90ff317f", + "lib/domain/use-cases/schedule_form_submission.dart": "3b05cab96ec691388eac5007f1791be25c88ef3cacfd34a2bb19c6fa69514d79", + "lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart": "c57bd8d8fc3204e920f677925d2ce5f1f6953062d38bef41c2cb1c51f465b6b5", + "lib/domain/use-cases/schedule_save_workflow.dart": "3eb8dcbbe095a31a972c95a56360b8275082b5a07a0e946d6d05c931ff971477", + "lib/domain/use-cases/update_schedule_form_submission_use_case.dart": "975215ea2d3e869b3da5c9e5c3acb643a79f869f18f81fa37289ac4ae5a8a4fa", + "lib/l10n/app_en.arb": "213a419f55776010ab7f980b83b19b56e13580d6a7c7c229eb692aea7e5f93bf", + "lib/l10n/app_ko.arb": "48b5c1fd482ac93d403b21c5e218e6645cb8cb55f36ddf8c99ca90f850252db0", + "lib/l10n/app_localizations.dart": "7298fb334a699579aaac77fc05dd354f32ba9bd714968f8cf03fe403a0318ec6", + "lib/l10n/app_localizations_en.dart": "14c3741c7e7307f61673d994ad5e44cd742a5f5e26b80fd9783225286ecee827", + "lib/l10n/app_localizations_ko.dart": "660c8d17c39a7e305519876053aa4dbf6a945f8ad3b03ef4ce13abddf7847c25", + "lib/presentation/schedule_create/bloc/schedule_form_bloc.dart": "6642f6ad29d36434ecb3dbb879d09b6f2a30759c529f0d9ea5cdb68e28e5a69a", + "lib/presentation/schedule_create/bloc/schedule_form_event.dart": "9f7a765946c18aea23d8fb97917ce100d6f40bc2d52bd63027a3d472cd086b56", + "lib/presentation/schedule_create/bloc/schedule_form_state.dart": "b692aa1667462f547662c2c57beab1b2b8b70dd04a893e432761ce96f0b64f7c", + "lib/presentation/schedule_create/components/schedule_multi_page_form.dart": "10a04857519473eb7c83fa6beb5958d3c62fa6b3139147105d8d49a860b8453a", + "pubspec.lock": "1305211851a74cdea3bae6402409b9ac0108562751859b062bb90aeb34900d4f", + "pubspec.yaml": "71d14bb6731407309b150f89e548ec4d78fa0843087e1b2a8d1489425697eaf1", + "test/core/backup/backup_service_test.dart": "92d76777b8cc5a4205fa33075281997313cf50f99b38271edd4e16718d6903f2", + "test/core/database/database_index_test.dart": "ead0ee32b368a911832fb1e08e86ec684eb767949d7ee060022ad6c81ceec898", + "test/core/database/recurring_migration_test.dart": "03a0745c00ea247c55dea9f65b27a435b0492b217520c0f75fab4a898b672c0d", + "test/core/database/schedule_aggregate_migration_test.dart": "46bf6aa216d7e46c47730e8aa0e87216984b6551dd1ec015cfa3f7d84e619d91", + "test/data/daos/schedule_dao_test.dart": "bc500cd4ebd497379c306ae2579a8619acab9795a580e73759e968a37707acfb", + "test/data/data_sources/schedule_local_data_source_test.dart": "a7c1f55b98887dcf2538f608070545cc64af145f5617b290bc5589e10511a365", + "test/data/repositories/recurring_schedule_repository_test.dart": "60969859e40aaf9b5301d5c1340c6ff201bb95466b50ce1369a5677f751cec05", + "test/data/repositories/schedule_aggregate_repository_test.dart": "dfa8ce81dd9f8f54fd3bdcd00354c88c921d37c615684fdf984942e47a2be763", + "test/data/repositories/schedule_aggregate_snapshot_test.dart": "50531d54e9eed6b93414185c62326f5cd6489c46eaf543bb08848511518efbca", + "test/domain/use-cases/create_schedule_form_submission_use_case_test.dart": "317a45914cb0490bb54fe25cfbdbb213ff0a95697820834774dd2936f88d6e67", + "test/domain/use-cases/update_schedule_form_submission_use_case_test.dart": "5fd739f2cbaada8a787d14d1af8dd7b9718df86027d356919ed7669b5f78b6a9", + "test/fixtures/database/README.md": "1e4068750433db1b18c5936216786128def1088f532d5a9486d9e9405625f168", + "test/fixtures/database/schema_v1.sql": "782cd3a3cfdca56c67f896a7fbdfa9791a349f77b2b65e21c7f0c0f2ce4a121d", + "test/fixtures/database/schema_v2.sql": "a7ec3c57f1c86a58ef45c3417abe187fa4e44a0be87e3861a7182cb3ab02d7c5", + "test/presentation/recurring/recurring_form_integration_test.dart": "6d935c38090713b0a729f8fe70a7ff4ff018d4de0f8fa508ab201040988422c9", + "test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart": "15bffe233e663f55d6588533dd272ccdd8658ef11be709e3708c8241cf18c954", + "test/presentation/schedule_create/components/schedule_multi_page_form_test.dart": "fb0ad4e2a2235f52d23466ae4b14447c37b04d7db12deff8d6f3319a7d2f197e" + }, + "raw_evidence_archive_inputs": { + "/tmp/a08-source-freeze.json": { + "sha256": "53f354317242075bb0c6ce7b02bdad8282e29ea58752fef8f3ee45f970c3a740", + "bytes": 6539 + }, + "/tmp/a08-source-freeze-v1.json": { + "sha256": "e7e9cfb8fd270bf85034eaaf95b35bf8decae940f859f12abfef642f729e343c", + "bytes": 6097 + }, + "/tmp/a08-source-freeze-v2.json": { + "sha256": "75f3f3f607495b8e0be3b9ed3a9e7eb4cdf8b5ef053d5fbe3edbb34990985a70", + "bytes": 6657 + }, + "/tmp/a08-source-freeze-v3.json": { + "sha256": "40c22c1505b5ea38b1434904d26bd1c36a531996ac917234cc428f5528002beb", + "bytes": 6520 + }, + "/tmp/a08-negative-proof.json": { + "sha256": "b697d371af0118e9fe899b81d09cfd744fade5b1a47663d1ca8cdb45ef88fb5f", + "bytes": 836 + }, + "/tmp/a08-negative.log": { + "sha256": "8f907e05b2250f2698569cd3006d0b126674549d71e86fc292eefb0408177f09", + "bytes": 1603 + }, + "/tmp/a08-negative-test.dart": { + "sha256": "692bce0eef10bde945c34f2741fa30f6ec7fa5964e605c638913ce8a85134e60", + "bytes": 3502 + }, + "/tmp/a08-review-transcript.md": { + "sha256": "f0017f580c17c2756db8fe06efc49da9b21b84808b36013b1cf2166737513947", + "bytes": 31627 + }, + "/tmp/a08-handoff.md": { + "sha256": "7a77842f0bb93f8ee890b58021d3e9cfc9dfece5300c9484d960e8d94527ad15", + "bytes": 8425 + }, + "/tmp/a08-codegen-final-v3.log": { + "sha256": "0f16236d9c8e2025d3cc44a0373bd8d31a73db2de9e469b021d7bdf018ff67e4", + "bytes": 1889 + }, + "/tmp/a08-l10n-freeze.log": { + "sha256": "97362974d3ec503ef18e205dbcc7d8e1ca57e5426c80a4966c6608571fa88609", + "bytes": 161 + }, + "/tmp/a08-focused-final.log": { + "sha256": "0ff59fda9f347ad7c5e618a9eb28fb732c89fd90d2997baa96309ff6eb3ae092", + "bytes": 28727 + }, + "/tmp/a08-full-fix-focused.log": { + "sha256": "c5062462e3edbc630aa30b67161c637e982c31cefcaa77515d91c6624ebbfacb", + "bytes": 15360 + }, + "/tmp/a08-focused-v3.log": { + "sha256": "c6e445464ece78dcb03fd32876a1793fb5c24972a566cf0328a946a5e0947619", + "bytes": 15211 + }, + "/tmp/a08-ordering-focused.log": { + "sha256": "9945f8c06d79a8cbe4ecc62476ab8ef5242dafe0c439030a280fc2d352c7421a", + "bytes": 2962 + }, + "/tmp/a08-materialization-probe.log": { + "sha256": "52ddcfe637cb22c6ea0009b09b1d43d74410905215bf21f29b2221bb28b61e99", + "bytes": 184 + }, + "/tmp/a08-materialization-probe_test.dart": { + "sha256": "ef383b108b63fe21db87a919ddf4ada20879791878ef956710a362681f265d17", + "bytes": 2324 + }, + "/tmp/a08-materialization-final-focused.log": { + "sha256": "7a120e1964404e4895db3b80014b90cfe6f740975a4e6a9be6008febb2871dd0", + "bytes": 3607 + }, + "/tmp/a08-analyze-final.log": { + "sha256": "253e6cd543d700598beaf075ddc943e038d2cf5fa08e5b1e260f10c940a9a12d", + "bytes": 96 + }, + "/tmp/a08-full-coverage.log": { + "sha256": "5f0d93ef8488ce1ebd743e6221d9b3de48a1bd5c363dcb388432e5d70790b7d7", + "bytes": 230982 + }, + "/tmp/a08-full-coverage-first-failure.log": { + "sha256": "12725fbaaa6a7657b28060894bf95f7b47a635a0548d4158ce91644862db0258", + "bytes": 260376 + }, + "/tmp/a08-full-coverage-before-ordering.log": { + "sha256": "bd004887598f1943a6b63b2f03d9a6bf88259c6a027cd907b805b3726060bfe9", + "bytes": 131499 + }, + "/tmp/a08-coverage-gate.log": { + "sha256": "197d63302fe72600f85a8cb3f4fba0c94bf875c120cd6a8aea8d21351e720a8b", + "bytes": 81 + }, + "/tmp/a08-generated-policy.log": { + "sha256": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "bytes": 113 + }, + "/tmp/a08-local-only.log": { + "sha256": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "bytes": 82 + }, + "/tmp/a08-python-tests.log": { + "sha256": "bf82059427ffe9b6f0424d5d1d0cce246bd4ae3114fef7fdb2d5aed0bab949f6", + "bytes": 1996 + }, + "/tmp/a08-android-manifest.log": { + "sha256": "60ac45f9be8ec739ed709fa43c33c8f51a94665b6bdea633dd65e27529d13c01", + "bytes": 151 + }, + "/tmp/a08-diff-check.log": { + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "bytes": 0 + }, + "/tmp/a08-historical-dump.log": { + "sha256": "ea9b1ee49d5da13cbca47fe908e35c0ab4786083f6771fe0bdf47930e1cf885e", + "bytes": 140 + } + }, + "root_review": { + "source_and_committed_blobs_match": true, + "source_file_count": 49, + "widget_captures_reviewed": 4, + "capture_scope": "Synthetic KO/EN 430x932 widget captures at 200%; new rollback/delivery-pending bodies and actions reviewed; broader underlay form labels remain U08 scope.", + "remote_ci": "pending", + "issue_remains_open": true + }, + "agent_raw_artifacts": { + "/tmp/a08-source-freeze.json": "53f354317242075bb0c6ce7b02bdad8282e29ea58752fef8f3ee45f970c3a740", + "/tmp/a08-source-freeze-v1.json": "e7e9cfb8fd270bf85034eaaf95b35bf8decae940f859f12abfef642f729e343c", + "/tmp/a08-source-freeze-v2.json": "75f3f3f607495b8e0be3b9ed3a9e7eb4cdf8b5ef053d5fbe3edbb34990985a70", + "/tmp/a08-source-freeze-v3.json": "40c22c1505b5ea38b1434904d26bd1c36a531996ac917234cc428f5528002beb", + "/tmp/a08-negative-proof.json": "b697d371af0118e9fe899b81d09cfd744fade5b1a47663d1ca8cdb45ef88fb5f", + "/tmp/a08-negative.log": "8f907e05b2250f2698569cd3006d0b126674549d71e86fc292eefb0408177f09", + "/tmp/a08-negative-test.dart": "692bce0eef10bde945c34f2741fa30f6ec7fa5964e605c638913ce8a85134e60", + "/tmp/a08-review-transcript.md": "f0017f580c17c2756db8fe06efc49da9b21b84808b36013b1cf2166737513947", + "/tmp/a08-handoff.md": "7a77842f0bb93f8ee890b58021d3e9cfc9dfece5300c9484d960e8d94527ad15", + "/tmp/a08-analyze-final.log": "253e6cd543d700598beaf075ddc943e038d2cf5fa08e5b1e260f10c940a9a12d", + "/tmp/a08-full-coverage.log": "5f0d93ef8488ce1ebd743e6221d9b3de48a1bd5c363dcb388432e5d70790b7d7", + "/tmp/a08-full-coverage-first-failure.log": "12725fbaaa6a7657b28060894bf95f7b47a635a0548d4158ce91644862db0258", + "/tmp/a08-full-coverage-before-ordering.log": "bd004887598f1943a6b63b2f03d9a6bf88259c6a027cd907b805b3726060bfe9", + "/tmp/a08-focused-final.log": "0ff59fda9f347ad7c5e618a9eb28fb732c89fd90d2997baa96309ff6eb3ae092", + "/tmp/a08-focused-v3.log": "c6e445464ece78dcb03fd32876a1793fb5c24972a566cf0328a946a5e0947619", + "/tmp/a08-materialization-final-focused.log": "7a120e1964404e4895db3b80014b90cfe6f740975a4e6a9be6008febb2871dd0", + "/tmp/a08-materialization-probe.log": "52ddcfe637cb22c6ea0009b09b1d43d74410905215bf21f29b2221bb28b61e99", + "/tmp/a08-materialization-probe_test.dart": "ef383b108b63fe21db87a919ddf4ada20879791878ef956710a362681f265d17", + "/tmp/a08-coverage-gate.log": "197d63302fe72600f85a8cb3f4fba0c94bf875c120cd6a8aea8d21351e720a8b", + "/tmp/a08-generated-policy.log": "6fbaa122fa71317c57c6dd9ac5e5f3a56588d95a9827892da05702283937607a", + "/tmp/a08-local-only.log": "587fabc88d7ad8a721a0e2ed2feb1e5400471f16be9bc1f9614fae6ee79ca56a", + "/tmp/a08-python-tests.log": "bf82059427ffe9b6f0424d5d1d0cce246bd4ae3114fef7fdb2d5aed0bab949f6", + "/tmp/a08-android-manifest.log": "60ac45f9be8ec739ed709fa43c33c8f51a94665b6bdea633dd65e27529d13c01", + "/tmp/a08-diff-check.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "/tmp/a08-codegen-final-v3.log": "0f16236d9c8e2025d3cc44a0373bd8d31a73db2de9e469b021d7bdf018ff67e4", + "/tmp/a08-l10n-freeze.log": "97362974d3ec503ef18e205dbcc7d8e1ca57e5426c80a4966c6608571fa88609", + "/tmp/a08-historical-dump.log": "ea9b1ee49d5da13cbca47fe908e35c0ab4786083f6771fe0bdf47930e1cf885e", + "/tmp/a08-ui/en-delivery-pending.png": "15eca6d2860787db4247be58146000b6bb8742ff6179c8d5ee9481b3c4d9a05b", + "/tmp/a08-ui/en-rollback.png": "04f2bf551f26a6813fbce8573794f0701baaf0ced617b77b8f0d3e83f10d1920", + "/tmp/a08-ui/ko-delivery-pending.png": "daccffe2454d4c300a8f4925a6a45e8e7b925b09824b3f98ea824a6fedb8c115", + "/tmp/a08-ui/ko-rollback.png": "3c732763d94fade1caa35d69aaf2e97e995e0d568a28a4e93628d9a5cc1ef976" + } +} diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-analyze-final.log b/plans/audit-2026-09-23/artifacts/a08/a08-analyze-final.log new file mode 100644 index 00000000..c56ef291 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-analyze-final.log @@ -0,0 +1,2 @@ +Analyzing OnTime-front... +No issues found! (ran in 2.3s) diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-android-manifest.log b/plans/audit-2026-09-23/artifacts/a08/a08-android-manifest.log new file mode 100644 index 00000000..29c18cf1 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-android-manifest.log @@ -0,0 +1 @@ +{"manifest": "android/app/src/main/AndroidManifest.xml", "sha256": "6b16f0fbaf8febcc2f9647450e40ca498462e2142f279c90666f86f77f14f210", "failures": []} diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-codegen-final-v3.log b/plans/audit-2026-09-23/artifacts/a08/a08-codegen-final-v3.log new file mode 100644 index 00000000..32278150 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-codegen-final-v3.log @@ -0,0 +1,22 @@ +W These options have been removed and were ignored: --delete-conflicting-outputs + 0s freezed on 504 inputs; lib/core/backup/backup_crypto.dart + 0s freezed on 504 inputs: 1 skipped; lib/core/backup/backup_file_export_port.dart +W SDK language version 3.12.0 is newer than `analyzer` language version 3.10.0. Run `flutter packages upgrade`. + 5s freezed on 504 inputs: 3 skipped, 1 no-op; spent 5s analyzing; lib/core/backup/recurring_backup_data.dart + 6s freezed on 504 inputs: 311 skipped, 22 no-op; spent 5s analyzing; lib/presentation/shared/router/route_arguments.dart + 6s freezed on 504 inputs: 481 skipped, 23 no-op; spent 5s analyzing + 0s json_serializable on 1008 inputs; lib/core/backup/backup_crypto.dart + 1s json_serializable on 1008 inputs: 924 skipped, 84 no-op + 0s drift_dev on 4032 inputs; lib/core/backup/backup_crypto.dart + 0s drift_dev on 4032 inputs: 1023 skipped, 96 same; lib/data/daos/preparation_user_dao.freezed.dart + 1s drift_dev on 4032 inputs: 3780 skipped, 175 same, 77 no-op + 0s source_gen:combining_builder on 2016 inputs; lib/core/backup/backup_crypto.dart + 0s source_gen:combining_builder on 2016 inputs: 2009 skipped, 7 same + 0s mockito:mockBuilder on 1184 inputs; test/core/backup/backup_crypto_test.dart + 0s mockito:mockBuilder on 1184 inputs: 1143 skipped, 41 no-op + 0s injectable_generator:injectable_builder on 5216 inputs; lib/core/backup/backup_crypto.dart + 0s injectable_generator:injectable_builder on 5216 inputs: 24 skipped, 1 same; lib/core/backup/backup_service.dart.types.temp.dart + 0s injectable_generator:injectable_builder on 5216 inputs: 5132 skipped, 15 same, 69 no-op + 0s injectable_generator:injectable_config_builder on 5216 inputs; lib/core/backup/backup_crypto.dart + 0s injectable_generator:injectable_config_builder on 5216 inputs: 5193 skipped, 23 no-op + Built with build_runner/aot in 10s; wrote 197 outputs. diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-coverage-gate.log b/plans/audit-2026-09-23/artifacts/a08/a08-coverage-gate.log new file mode 100644 index 00000000..7f2913f9 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-coverage-gate.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Coverage: 88.96% (14113/15864 lines) diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-diff-check.log b/plans/audit-2026-09-23/artifacts/a08/a08-diff-check.log new file mode 100644 index 00000000..e69de29b diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-focused-final.log b/plans/audit-2026-09-23/artifacts/a08/a08-focused-final.log new file mode 100644 index 00000000..4861265d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-focused-final.log @@ -0,0 +1,149 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: schedule fault rolls back the whole aggregate and same intent retries +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: step fault rolls back the whole aggregate and same intent retries +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: revision fault rolls back the whole aggregate and same intent retries +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: edit failure after old preparation deletion preserves old aggregate and revision +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: concurrent and recreated repository lost receipt retry commit once +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: same id other input conflicts instead of upsert +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: semantic no-op preserves owned step IDs and revision +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: deleted create retry and recreated same id reject old intent +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: new store same revision and fresh memory generation rejects old draft +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: unrelated durable create conflict is explicit; reviewed new intent succeeds +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:00 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:00 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:00 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:00 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:00 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:00 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:00 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: file database close/reopen with new gate verifies committed receipt +00:00 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:00 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:00 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:00 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:00 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +WARNING (drift): It looks like you've created the database class AppDatabase multiple times. When these two databases use the same QueryExecutor, race conditions will occur and might corrupt the database. +Try to follow the advice at https://drift.simonbinder.eu/faq/#using-the-database or, if you know what you're doing, set driftRuntimeOptions.dontWarnAboutMultipleDatabases = true +Here is the stacktrace from when the database was opened a second time: +#0 GeneratedDatabase._handleInstantiated (package:drift/src/runtime/api/db_base.dart:96:30) +#1 GeneratedDatabase._whenConstructed (package:drift/src/runtime/api/db_base.dart:73:12) +#2 new GeneratedDatabase (package:drift/src/runtime/api/db_base.dart:64:5) +#3 new _$AppDatabase (package:on_time_front/core/database/database.g.dart:6499:36) +#4 new AppDatabase.forTesting (package:on_time_front/core/database/database.dart) +#5 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart:105:37) + +#6 Declarer.test.. (package:test_api/src/backend/declarer.dart:253:15) + +#7 Declarer.test. (package:test_api/src/backend/declarer.dart:250:11) + +#8 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:318:9) + +This warning will only appear on debug builds. +00:00 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:00 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:00 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 2 upgrades atomically and identities survive reopen/upsert +WARNING (drift): It looks like you've created the database class AppDatabase multiple times. When these two databases use the same QueryExecutor, race conditions will occur and might corrupt the database. +Try to follow the advice at https://drift.simonbinder.eu/faq/#using-the-database or, if you know what you're doing, set driftRuntimeOptions.dontWarnAboutMultipleDatabases = true +Here is the stacktrace from when the database was opened a second time: +#0 GeneratedDatabase._handleInstantiated (package:drift/src/runtime/api/db_base.dart:96:30) +#1 GeneratedDatabase._whenConstructed (package:drift/src/runtime/api/db_base.dart:73:12) +#2 new GeneratedDatabase (package:drift/src/runtime/api/db_base.dart:64:5) +#3 new _$AppDatabase (package:on_time_front/core/database/database.g.dart:6499:36) +#4 new AppDatabase.forTesting (package:on_time_front/core/database/database.dart) +#5 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart:105:37) + +#6 Declarer.test.. (package:test_api/src/backend/declarer.dart:253:15) + +#7 Declarer.test. (package:test_api/src/backend/declarer.dart:250:11) + +#8 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:318:9) + +This warning will only appear on debug builds. +00:00 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: following receipt survives file close reopen and deletion of original occurrence +00:00 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: following receipt survives file close reopen and deletion of original occurrence +00:00 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: following receipt survives file close reopen and deletion of original occurrence +00:00 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: following receipt survives file close reopen and deletion of original occurrence +00:00 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: following receipt survives file close reopen and deletion of original occurrence +00:00 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: following receipt survives file close reopen and deletion of original occurrence +00:00 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: following receipt survives file close reopen and deletion of original occurrence +00:00 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:00 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:00 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:01 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:02 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: cancel preserves freshness and releases gate for a retry +00:02 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:02 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:02 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:02 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:02 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:02 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:03 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:03 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:03 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:03 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:03 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:03 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:04 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:04 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:04 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:04 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:04 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:04 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:04 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edited name and place are submitted from form steps +00:04 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry en +00:04 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry en +00:05 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:05 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:05 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:05 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:05 +112: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-focused-v3.log b/plans/audit-2026-09-23/artifacts/a08/a08-focused-v3.log new file mode 100644 index 00000000..6e8fa4e9 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-focused-v3.log @@ -0,0 +1,62 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: occurrence owned definition invalidates series root receipt dependency +00:00 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:00 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:00 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:02 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:02 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: planning snapshot cannot combine old schedule with new preparation +00:02 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: file database close/reopen with new gate verifies committed receipt +00:02 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:02 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: template and definition trigger versions wake actual dependent schedule watch +00:02 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: new draft reads default preparation and revision in one real transaction +00:02 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: following receipt survives file close reopen and deletion of original occurrence +00:02 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:03 +60: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-before-ordering.log b/plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-before-ordering.log new file mode 100644 index 00000000..d886ff4f --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-before-ordering.log @@ -0,0 +1,657 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +WARNING (drift): It looks like you've created the database class AppDatabase multiple times. When these two databases use the same QueryExecutor, race conditions will occur and might corrupt the database. +Try to follow the advice at https://drift.simonbinder.eu/faq/#using-the-database or, if you know what you're doing, set driftRuntimeOptions.dontWarnAboutMultipleDatabases = true +Here is the stacktrace from when the database was opened a second time: +#0 GeneratedDatabase._handleInstantiated (package:drift/src/runtime/api/db_base.dart:96:30) +#1 GeneratedDatabase._whenConstructed (package:drift/src/runtime/api/db_base.dart:73:12) +#2 new GeneratedDatabase (package:drift/src/runtime/api/db_base.dart:64:5) +#3 new _$AppDatabase (package:on_time_front/core/database/database.g.dart:6499:36) +#4 new AppDatabase.forTesting (package:on_time_front/core/database/database.dart) +#5 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart:105:37) + +#6 Declarer.test.. (package:test_api/src/backend/declarer.dart:253:15) + +#7 Declarer.test. (package:test_api/src/backend/declarer.dart:250:11) + +#8 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:318:9) + +This warning will only appear on debug builds. +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 2 upgrades atomically and identities survive reopen/upsert +WARNING (drift): It looks like you've created the database class AppDatabase multiple times. When these two databases use the same QueryExecutor, race conditions will occur and might corrupt the database. +Try to follow the advice at https://drift.simonbinder.eu/faq/#using-the-database or, if you know what you're doing, set driftRuntimeOptions.dontWarnAboutMultipleDatabases = true +Here is the stacktrace from when the database was opened a second time: +#0 GeneratedDatabase._handleInstantiated (package:drift/src/runtime/api/db_base.dart:96:30) +#1 GeneratedDatabase._whenConstructed (package:drift/src/runtime/api/db_base.dart:73:12) +#2 new GeneratedDatabase (package:drift/src/runtime/api/db_base.dart:64:5) +#3 new _$AppDatabase (package:on_time_front/core/database/database.g.dart:6499:36) +#4 new AppDatabase.forTesting (package:on_time_front/core/database/database.dart) +#5 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart:105:37) + +#6 Declarer.test.. (package:test_api/src/backend/declarer.dart:253:15) + +#7 Declarer.test. (package:test_api/src/backend/declarer.dart:250:11) + +#8 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:318:9) + +This warning will only appear on debug builds. +00:01 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: failed schema3 identity backfill rolls back additive columns and preserves schema2 +00:01 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: future database version is rejected without downgrade or data mutation +00:01 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:01 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:01 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: reopen retries only unconfirmed ResetStep.key deletion stage +00:02 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unverified intent write reports no completion and never deletes data +00:02 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:02 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/database_index_test.dart: creates lookup indexes for schedule and preparation DAO predicates +00:03 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/database_index_test.dart: rejects in-place upgrades across the one-way local cutover +00:03 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:03 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:03 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: only complete iOS OS observations resolve reconstructed uncertainty +00:03 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeUnmapped remains partial without guessing ownership +00:03 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeError remains partial without guessing ownership +00:03 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS fallbackError remains partial without guessing ownership +00:03 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS unownedNotification remains partial without guessing ownership +00:03 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: secure reset marker resumes deletion but Android uncertainty remains +00:03 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: explicit reset after damage cleans quarantine only after complete receipt +00:03 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":2} +00:03 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"complete","completed":[],"ownership":[]} +00:03 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"none","completed":[],"ownership":[{"provider":"localNotification","scheduleId":"a","id":1,"pending":true},{"provider":"localNotification","scheduleId":"b","id":1,"pending":true}]} +00:03 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: read I/O failure does not quarantine or authorize deletion +00:03 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryMarked recovers through a new owner +00:03 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:03 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:03 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at verified recovers through a new owner +00:03 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at renamed recovers through a new owner +00:03 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at readBack recovers through a new owner +00:03 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryVerified recovers through a new owner +00:03 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryPublished recovers through a new owner +00:03 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: first pending quarantine crash resumes without deleting user data +00:03 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: unverified reconstructed reset journal cannot start deletion or OS cancellation +00:03 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: true +00:03 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: false +00:03 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: legacy marker clear failure stays closed and fresh owner retries after journal verification +00:03 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: journal-only uncertainty from temporary iOS observation failure converges on fresh startup +00:03 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: notification unmappedCount keeps uncertainty even with an empty entry list +00:03 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: invalid UTF-8 is syntax corruption, not an empty journal +00:03 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: quarantine alone cannot be mistaken for a new installation +00:03 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future staged version is not overwritten beside a valid committed journal +00:03 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: recovery refuses a future staged format even beside damaged main +00:04 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:04 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: normal civil time resolves to one absolute occurrence +00:04 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:05 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:05 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:05 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:05 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: preserves case and surrounding spaces +00:05 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: rejects passwords outside the 15 to 128 code point boundary +00:05 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:06 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:06 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:06 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:06 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:07 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/logging/app_logger_test.dart: logging source scan Dart app code uses AppLogger instead of raw debugPrint +00:07 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:07 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:07 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:07 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:07 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:07 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:07 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:07 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:08 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:08 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:08 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:08 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: independent file retains only cancellation identity across reopen +00:09 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at written reopens a complete committed generation +00:09 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: initializeLaunchHandling dispatches cold-start alarm payload +00:09 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at verified reopens a complete committed generation +00:09 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at verified reopens a complete committed generation +00:09 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities maps native alarm provider capabilities +00:09 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:09 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:09 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:09 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:09 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:09 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:09 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:09 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm sends the native alarm contract payload +00:09 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at readBack reopens a complete committed generation +00:09 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at readBack reopens a complete committed generation +00:09 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at readBack reopens a complete committed generation +00:09 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at readBack reopens a complete committed generation +00:09 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm reports missing native platform as unconfirmed +00:09 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: staged first write is preserved and never reported as an empty installation +00:09 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: staged first write is preserved and never reported as an empty installation +00:09 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelAllNativeAlarms cancels every provided record in order +00:09 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: truncated staged first write is preserved and blocks new ownership +00:09 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: truncated staged first write is preserved and blocks new ownership +00:09 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {broken +00:09 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {"version":2} +00:09 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {"version":1,"ownership":[]} +00:09 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: backup exclusion failure prevents creating ownership bytes +00:09 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: only a fully completed reset journal can be removed +00:10 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: duplicate actual platform IDs are rejected instead of hiding ownership +00:10 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: impossible completion states cannot be written over a valid journal +00:10 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=false snapshot after persistence +00:10 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=true snapshot after persistence +00:10 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=false snapshot after persistence +00:10 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=true snapshot after persistence +00:10 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: legacy and corrupted content cannot bypass reconciliation at either provider +00:10 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancel uses the plugin API for pending and presented IDs, never cancelAll +00:10 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancellation errors are visible to reconciliation +00:10 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/app_metadata_service_test.dart: package info metadata provider returns runtime app metadata +00:10 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: owned legacy pending is cancelled without clearing step notifications +00:10 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: cancel failure preserves only ownership tombstone and retry converges +00:10 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:10 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: successful cancel call without readback removal is not success +00:11 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: cold launch collects once and concurrent initialization is single flight +00:11 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:11 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:11 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:11 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android read failure is unknown, not absent +00:11 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android read failure is unknown, not absent +00:11 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: getter failure releases single flight and callback still works before retry +00:11 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cancel needs public success and post read-back +00:11 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback B arriving while cold A getter waits wins, raw legacy fields removed +00:11 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache absence does not hide cancel failure +00:11 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache absence does not hide cancel failure +00:11 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: Noop to installed delegate cannot let slow cold A overwrite native B +00:11 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: Noop to installed delegate cannot let slow cold A overwrite native B +00:11 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: Noop to installed delegate cannot let slow cold A overwrite native B +00:11 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS read failure is unknown, not absent +00:11 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: data replacement invalidates delayed initial launch +00:11 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cancel needs public success and post read-back +00:11 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cache absence does not hide cancel failure +00:11 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS remaining ID and failed post query are unconfirmed cancellations +00:11 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:11 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:11 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:11 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: platform cleanup failure is visible and retryable, without raw content +00:11 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: DB-unavailable cleanup removes legacy raw and does not touch unrelated data +00:11 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: localizedNotificationText selects Korean text only for Korean locale +00:11 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: isScheduleAlarmPayload detects native alarm payloads without suppressing schedule notifications +00:11 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule notification payload to the schedule start screen +00:11 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes five-minute prompts as early-start schedule starts +00:11 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule and preparation updates to the alarm screen +00:11 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString ignores null, invalid, and unrelated payloads +00:11 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: invalid schedule hints never route to nearest preparation +00:11 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForData routes decoded local payload data with the same rules +00:11 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:11 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: explicit exact denial retries once and returns actual approximate receipt +00:12 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unrelated provider error is never converted to approximate success +00:12 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: failed approximate retry stays failed and is not retried again +00:12 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: unknown timing permission uses approximate rather than claiming exact +00:12 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: display denial blocks scheduling even with exact access +00:12 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: settings response is not proof of a grant: permission is reread +00:12 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: iOS timing access is unsupported without calling Android plugin +00:12 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:12 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:12 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:12 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:12 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:12 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:12 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: file database close/reopen with new gate verifies committed receipt +00:12 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: file database close/reopen with new gate verifies committed receipt +00:12 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: file database close/reopen with new gate verifies committed receipt +00:12 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=false missing=false without overwriting durable first start +00:12 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:12 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:12 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:13 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:13 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:13 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:13 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:13 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:13 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:13 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: completed and deleted occurrences cannot restart +00:13 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: template and definition trigger versions wake actual dependent schedule watch +00:13 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:13 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:13 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:13 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: stale first occurrence approval asks for review without changing durable data +00:13 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:13 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:13 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:13 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:14 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: an override cannot be moved into another occurrence preparation window +00:14 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:14 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: score reset starts a new aggregate without recounting old completions +00:14 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: spare time preserves preferences, scores and export metadata +00:14 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: repeated initial creation cannot replace an existing profile +00:14 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: same preferences and empty score reset are exact no-ops +00:14 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:14 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: concurrent finishes count distinct schedules once and duplicate only once +00:14 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:14 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:14 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:14 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: multiple preparation steps are a no-op even when caller omits links +00:14 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:14 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:14 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:14 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:15 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: schedule fault rolls back the whole aggregate and same intent retries +00:15 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: step fault rolls back the whole aggregate and same intent retries +00:15 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: revision fault rolls back the whole aggregate and same intent retries +00:15 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: edit failure after old preparation deletion preserves old aggregate and revision +00:15 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: concurrent and recreated repository lost receipt retry commit once +00:15 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: same id other input conflicts instead of upsert +00:15 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: semantic no-op preserves owned step IDs and revision +00:15 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:15 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: deleted create retry and recreated same id reject old intent +00:15 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: new store same revision and fresh memory generation rejects old draft +00:15 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: unrelated durable create conflict is explicit; reviewed new intent succeeds +00:15 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: start between draft and commit conflicts and preserves protected fields +00:15 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: same shared place label edit copies ownership +00:15 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: invalid custom input is rejected atomically 0:00:00.000000 +00:15 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: invalid custom input is rejected atomically 0:01:59.000000 +00:15 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: create cannot inject outcome or frozen metadata +00:15 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring duplicate create has durable receipt and rejects changed input +00:15 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:15 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:15 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:15 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:15 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:15 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:15 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:15 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring recurring_schedule_segments fault rolls back definitions series materialization and revision +00:15 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedule date ranges as start-inclusive and end-exclusive +00:15 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring schedules fault rolls back definitions series materialization and revision +00:15 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring schedules fault rolls back definitions series materialization and revision +00:15 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring users fault rolls back definitions series materialization and revision +00:15 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: following split keeps stable root through clock reversal and recovers lost receipt +00:15 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: semantic same following save does not split or increase revision +00:15 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: root receipt rejects dependency place mutation after following commit +00:15 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: custom step 0 insertion failure has no partial aggregate +00:15 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: custom step 1 insertion failure has no partial aggregate +00:15 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: custom step 2 insertion failure has no partial aggregate +00:15 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: clock reaching preparation start protects unchanged database baseline +00:15 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.occurrence preparation_definition_steps failure preserves prior complete series +00:15 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.occurrence users failure preserves prior complete series +00:15 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_data_source_contract_test.dart: schedule local data source contract does not expose domain entities +00:15 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.following preparation_definition_steps failure preserves prior complete series +00:15 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.following users failure preserves prior complete series +00:15 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recreated create receipt cannot validate old baseline even with reused mutation identity +00:16 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: occurrence owned definition invalidates series root receipt dependency +00:16 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: legacy persisted row withPlace=false cannot rewind identity version or receipt +00:16 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: legacy persisted row withPlace=true cannot rewind identity version or receipt +00:16 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: saves and loads the early start timestamp per schedule +00:16 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:16 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:16 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:16 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps places and preparation steps to database rows +00:16 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:16 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:17 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:17 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getUserById returns null for an unknown user +00:17 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getAllUsers returns all persisted users as domain entities +00:17 +367: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart +00:18 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:18 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:18 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:18 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:18 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:18 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:18 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:18 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:18 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationUsersByUserId should keep remaining preparation steps when a link is broken +00:18 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:18 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:18 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:18 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: updatePreparationUser should update a preparation step +00:18 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should return one preparation step by id +00:18 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: deleteSchedule should delete a schedule from the database +00:18 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations createPreparationSchedule stores steps as an ordered linked list +00:18 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:18 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:18 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:18 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:18 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:18 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fifth weekday skips missing months while last weekday does not +00:18 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:18 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:18 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:18 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:18 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fall overlap requires an explicit first or second choice +00:18 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:18 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:18 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:18 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:18 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: invalid and ambiguous rule definitions are rejected +00:18 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:18 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:18 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:18 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation +00:18 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:18 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:18 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: skips a just-missed alarm instead of scheduling catch-up +00:18 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:18 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:18 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:18 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:18 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:18 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:18 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:18 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:18 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:18 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:18 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:18 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:18 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:18 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:18 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:18 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:18 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:18 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:18 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:18 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:18 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:18 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:18 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:18 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:18 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:18 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:18 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:18 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:18 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:18 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:18 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:18 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:18 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:18 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:18 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:18 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:18 +453: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart +00:19 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:19 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:19 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:19 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: reconciliation failures remain best-effort +00:19 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:19 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:20 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:20 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:20 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:20 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:20 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:20 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:20 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:20 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:20 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: pending callers share failure, then retry the same instance and timestamp +00:20 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: snapshot failure is partial and retry preserves a newer skip projection +00:20 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: replacement generation does not reuse an old flight or write old runtime +00:20 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: marker failure keeps durable run and a bounded retry preserves its clock +00:20 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish while start cleanup is pending cannot resurrect the run +00:20 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: dispose detaches the shared listener without releasing in-flight ownership +00:20 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:20 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure does not undo committed start or skip its snapshot +00:20 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure after finish still clears completed runtime +00:20 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:20 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:20 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:20 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:20 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:20 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:20 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:20 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:21 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:22 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: replacement generation invalidates preview with no additional effects +00:22 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:22 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: edit while cleanup awaits rejects transaction and exposes pending recovery +00:22 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:22 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:22 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:22 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failure alone stops; a request from completion starts another pass +00:22 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:23 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:23 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:23 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=false +00:23 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:23 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:23 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: a registry-owned ID with a conflicting route is cancelled before repair +00:23 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:23 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:23 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:23 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:23 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:23 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:23 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:23 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:23 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:23 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:23 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:23 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:23 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:23 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:24 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:24 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:24 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: confirmed fallback orphans are targeted; unrelated and unknown ownership survive +00:24 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:24 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native failure uses only the actual fallback receipt after native cleanup +00:24 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed native write cleanup forbids fallback and keeps cancellation identity +unhandled error during finalization of test: +/Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart +PathNotFoundException: Deletion failed, path = '/var/folders/7c/dbh5cdy53nq6xn7dn77q9vfr0000gn/T/flutter_tools.IvFAZb/flutter_test_listener.iIiidt' (OS Error: No such file or directory, errno = 2) +#0 _Directory._deleteSync (dart:io/directory_impl.dart:216:7) +#1 FileSystemEntity.deleteSync (dart:io/file_system_entity.dart:425:7) +#2 ForwardingFileSystemEntity.deleteSync (package:file/src/forwarding/forwarding_file_system_entity.dart:70:16) +#3 ErrorHandlingDirectory.deleteSync. (package:flutter_tools/src/base/error_handling_io.dart:715:22) +#4 _runSync (package:flutter_tools/src/base/error_handling_io.dart:1116:16) +#5 ErrorHandlingDirectory.deleteSync (package:flutter_tools/src/base/error_handling_io.dart:714:12) +#6 FlutterPlatform._createListenerDart. (package:flutter_tools/src/test/flutter_platform.dart:787:15) +#7 FlutterPlatform._startTest.finalize (package:flutter_tools/src/test/flutter_platform.dart:592:26) + +#8 asyncGuard. (package:flutter_tools/src/base/async_guard.dart:108:24) + + +unhandled error during finalization of test: +/Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart +PathNotFoundException: Deletion failed, path = '/var/folders/7c/dbh5cdy53nq6xn7dn77q9vfr0000gn/T/flutter_tools.IvFAZb/flutter_test_listener.tBQvxf' (OS Error: No such file or directory, errno = 2) +#0 _Directory._deleteSync (dart:io/directory_impl.dart:216:7) +#1 FileSystemEntity.deleteSync (dart:io/file_system_entity.dart:425:7) +#2 ForwardingFileSystemEntity.deleteSync (package:file/src/forwarding/forwarding_file_system_entity.dart:70:16) +#3 ErrorHandlingDirectory.deleteSync. (package:flutter_tools/src/base/error_handling_io.dart:715:22) +#4 _runSync (package:flutter_tools/src/base/error_handling_io.dart:1116:16) +#5 ErrorHandlingDirectory.deleteSync (package:flutter_tools/src/base/error_handling_io.dart:714:12) +#6 FlutterPlatform._createListenerDart. (package:flutter_tools/src/test/flutter_platform.dart:787:15) +#7 FlutterPlatform._startTest.finalize (package:flutter_tools/src/test/flutter_platform.dart:592:26) + +#8 asyncGuard. (package:flutter_tools/src/base/async_guard.dart:108:24) + + +Could not parse shell VM Service port message: HttpException: Connection closed before full header was received, uri = http://127.0.0.1:62096/KjmcT-cWK4Q=/ws +unhandled error during finalization of test: +/Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart +PathNotFoundException: Deletion failed, path = '/var/folders/7c/dbh5cdy53nq6xn7dn77q9vfr0000gn/T/flutter_tools.IvFAZb/flutter_test_listener.swEfED' (OS Error: No such file or directory, errno = 2) +#0 _Directory._deleteSync (dart:io/directory_impl.dart:216:7) +#1 FileSystemEntity.deleteSync (dart:io/file_system_entity.dart:425:7) +#2 ForwardingFileSystemEntity.deleteSync (package:file/src/forwarding/forwarding_file_system_entity.dart:70:16) +#3 ErrorHandlingDirectory.deleteSync. (package:flutter_tools/src/base/error_handling_io.dart:715:22) +#4 _runSync (package:flutter_tools/src/base/error_handling_io.dart:1116:16) +#5 ErrorHandlingDirectory.deleteSync (package:flutter_tools/src/base/error_handling_io.dart:714:12) +#6 FlutterPlatform._createListenerDart. (package:flutter_tools/src/test/flutter_platform.dart:787:15) +#7 FlutterPlatform._startTest.finalize (package:flutter_tools/src/test/flutter_platform.dart:592:26) + +#8 asyncGuard. (package:flutter_tools/src/base/async_guard.dart:108:24) + + +unhandled error during finalization of test: +/Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart +PathNotFoundException: Deletion failed, path = '/var/folders/7c/dbh5cdy53nq6xn7dn77q9vfr0000gn/T/flutter_tools.IvFAZb/flutter_test_listener.tXJ8yM' (OS Error: No such file or directory, errno = 2) +#0 _Directory._deleteSync (dart:io/directory_impl.dart:216:7) +#1 FileSystemEntity.deleteSync (dart:io/file_system_entity.dart:425:7) +#2 ForwardingFileSystemEntity.deleteSync (package:file/src/forwarding/forwarding_file_system_entity.dart:70:16) +#3 ErrorHandlingDirectory.deleteSync. (package:flutter_tools/src/base/error_handling_io.dart:715:22) +#4 _runSync (package:flutter_tools/src/base/error_handling_io.dart:1116:16) +#5 ErrorHandlingDirectory.deleteSync (package:flutter_tools/src/base/error_handling_io.dart:714:12) +#6 FlutterPlatform._createListenerDart. (package:flutter_tools/src/test/flutter_platform.dart:787:15) +#7 FlutterPlatform._startTest.finalize (package:flutter_tools/src/test/flutter_platform.dart:592:26) + +#8 asyncGuard. (package:flutter_tools/src/base/async_guard.dart:108:24) + + +One or more uncaught errors occurred shutting down: + Bad state: Cannot add event after closing. +Could not parse shell VM Service port message: DartDevelopmentServiceException: WebSocketChannelException: SocketException: Connection refused (OS Error: Connection refused, errno = 61), address = 127.0.0.1, port = 62105 +Unhandled exception: +PathNotFoundException: Cannot open file, path = '/var/folders/7c/dbh5cdy53nq6xn7dn77q9vfr0000gn/T/flutter_tools.IvFAZb/flutter_test_compiler.Iu4BU7/output.dill' (OS Error: No such file or directory, errno = 2) +#0 _checkForErrorResponse (dart:io/common.dart:58) +#1 _File.open. (dart:io/file_impl.dart:438) + +#2 _FileStreamConsumer.addStream. (dart:io/file_impl.dart:223) + diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-first-failure.log b/plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-first-failure.log new file mode 100644 index 00000000..2e1faf84 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-full-coverage-first-failure.log @@ -0,0 +1,1168 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +WARNING (drift): It looks like you've created the database class AppDatabase multiple times. When these two databases use the same QueryExecutor, race conditions will occur and might corrupt the database. +Try to follow the advice at https://drift.simonbinder.eu/faq/#using-the-database or, if you know what you're doing, set driftRuntimeOptions.dontWarnAboutMultipleDatabases = true +Here is the stacktrace from when the database was opened a second time: +#0 GeneratedDatabase._handleInstantiated (package:drift/src/runtime/api/db_base.dart:96:30) +#1 GeneratedDatabase._whenConstructed (package:drift/src/runtime/api/db_base.dart:73:12) +#2 new GeneratedDatabase (package:drift/src/runtime/api/db_base.dart:64:5) +#3 new _$AppDatabase (package:on_time_front/core/database/database.g.dart:6499:36) +#4 new AppDatabase.forTesting (package:on_time_front/core/database/database.dart) +#5 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart:105:37) + +#6 Declarer.test.. (package:test_api/src/backend/declarer.dart:253:15) + +#7 Declarer.test. (package:test_api/src/backend/declarer.dart:250:11) + +#8 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:318:9) + +This warning will only appear on debug builds. +00:01 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 2 upgrades atomically and identities survive reopen/upsert +WARNING (drift): It looks like you've created the database class AppDatabase multiple times. When these two databases use the same QueryExecutor, race conditions will occur and might corrupt the database. +Try to follow the advice at https://drift.simonbinder.eu/faq/#using-the-database or, if you know what you're doing, set driftRuntimeOptions.dontWarnAboutMultipleDatabases = true +Here is the stacktrace from when the database was opened a second time: +#0 GeneratedDatabase._handleInstantiated (package:drift/src/runtime/api/db_base.dart:96:30) +#1 GeneratedDatabase._whenConstructed (package:drift/src/runtime/api/db_base.dart:73:12) +#2 new GeneratedDatabase (package:drift/src/runtime/api/db_base.dart:64:5) +#3 new _$AppDatabase (package:on_time_front/core/database/database.g.dart:6499:36) +#4 new AppDatabase.forTesting (package:on_time_front/core/database/database.dart) +#5 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart:105:37) + +#6 Declarer.test.. (package:test_api/src/backend/declarer.dart:253:15) + +#7 Declarer.test. (package:test_api/src/backend/declarer.dart:250:11) + +#8 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:318:9) + +This warning will only appear on debug builds. +00:01 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: completed-journal unlink failure cannot replay deletion against new content +00:01 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:01 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:01 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:01 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:01 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:01 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_actions_test.dart: database is closed before file deletion and a file failure propagates +00:02 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:02 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:02 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:02 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:02 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/database_index_test.dart: creates lookup indexes for schedule and preparation DAO predicates +00:02 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/database_index_test.dart: rejects in-place upgrades across the one-way local cutover +00:03 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:03 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:03 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: only complete iOS OS observations resolve reconstructed uncertainty +00:03 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeUnmapped remains partial without guessing ownership +00:03 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS nativeError remains partial without guessing ownership +00:03 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS fallbackError remains partial without guessing ownership +00:03 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS unownedNotification remains partial without guessing ownership +00:03 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: secure reset marker resumes deletion but Android uncertainty remains +00:03 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: explicit reset after damage cleans quarantine only after complete receipt +00:03 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":2} +00:03 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"complete","completed":[],"ownership":[]} +00:03 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"none","completed":[],"ownership":[{"provider":"localNotification","scheduleId":"a","id":1,"pending":true},{"provider":"localNotification","scheduleId":"b","id":1,"pending":true}]} +00:03 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: read I/O failure does not quarantine or authorize deletion +00:03 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryMarked recovers through a new owner +00:03 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:03 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:03 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at verified recovers through a new owner +00:03 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at renamed recovers through a new owner +00:03 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at readBack recovers through a new owner +00:03 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryVerified recovers through a new owner +00:03 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryPublished recovers through a new owner +00:03 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: first pending quarantine crash resumes without deleting user data +00:03 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: unverified reconstructed reset journal cannot start deletion or OS cancellation +00:03 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: true +00:03 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: pending reset preserves its ownership and bypasses unrelated privacy cleanup +00:03 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: false +00:03 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: false +00:03 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: false +00:03 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: real corrupt registry marker is resolved only with complete iOS evidence: false +00:03 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: legacy marker clear failure stays closed and fresh owner retries after journal verification +00:03 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: journal-only uncertainty from temporary iOS observation failure converges on fresh startup +00:03 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: notification unmappedCount keeps uncertainty even with an empty entry list +00:03 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: invalid UTF-8 is syntax corruption, not an empty journal +00:03 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: quarantine alone cannot be mistaken for a new installation +00:03 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: normal civil time resolves to one absolute occurrence +00:03 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: spring-forward gap has no valid occurrence +00:03 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: fall-back overlap exposes first and second occurrence +00:04 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:04 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:05 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:05 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:05 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:05 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:05 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:05 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:05 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: preserves case and surrounding spaces +00:05 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: rejects passwords outside the 15 to 128 code point boundary +00:05 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:05 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:06 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: cancel preserves freshness and releases gate for a retry +00:06 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: write failure cannot mark success or expose provider error details +00:06 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:06 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:06 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:06 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:06 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:06 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:06 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:06 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:06 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: app filter reaches real iOS host as public.data; cancel is null +00:06 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:06 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:06 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:06 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:06 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:06 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:06 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:06 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:06 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_tap_router_test.dart: A in flight then B then A uses latest A rather than stale-key dedupe +00:06 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:06 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:07 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:07 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:07 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:07 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:07 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:07 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:07 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:07 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: corrupted backup is rejected without writes +00:07 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:08 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:09 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: initializeLaunchHandling dispatches cold-start alarm payload +00:09 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload dispatches resumed alarm payload +00:09 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities maps native alarm provider capabilities +00:09 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats missing platform implementation as unsupported +00:09 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats native platform errors as unsupported +00:09 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats null native response as unsupported +00:09 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: checkPermission and requestPermission map native wire values +00:09 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat missing platform implementation as unsupported +00:09 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat native platform errors as unsupported +00:09 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm sends the native alarm contract payload +00:09 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm exposes permission failures as scheduling exception +00:09 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm maps unsupported and generic native errors +00:09 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm sends cancellation payload for scheduled records +00:09 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm reports missing native platform as unconfirmed +00:09 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm maps native platform failures to scheduling exception +00:09 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelAllNativeAlarms cancels every provided record in order +00:09 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload ignores invalid and failed payload reads +00:09 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=false snapshot after persistence +00:09 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=true snapshot after persistence +00:09 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=false snapshot after persistence +00:09 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=true snapshot after persistence +00:09 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: legacy and corrupted content cannot bypass reconciliation at either provider +00:09 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancel uses the plugin API for pending and presented IDs, never cancelAll +00:09 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancellation errors are visible to reconciliation +00:10 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/app_metadata_service_test.dart: package info metadata provider returns runtime app metadata +00:10 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: owned legacy pending is cancelled without clearing step notifications +00:10 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: cancel failure preserves only ownership tombstone and retry converges +00:10 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:10 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: successful cancel call without readback removal is not success +00:10 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: cold launch collects once and concurrent initialization is single flight +00:10 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: null +00:10 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:10 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: successful empty or invalid launch is consumed without navigation: Instance of 'NotificationAppLaunchDetails' +00:10 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: getter failure releases single flight and callback still works before retry +00:10 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback B arriving while cold A getter waits wins, raw legacy fields removed +00:10 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback before delegate installation preserves only safe latest payload +00:10 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: Noop to installed delegate cannot let slow cold A overwrite native B +00:10 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: data replacement invalidates delayed initial launch +00:10 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:10 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android read failure is unknown, not absent +00:10 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cancel needs public success and post read-back +00:10 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache absence does not hide cancel failure +00:10 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android remaining ID and failed post query are unconfirmed cancellations +00:10 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS OS observation preserves source and minimal ownership +00:10 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS read failure is unknown, not absent +00:10 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cancel needs public success and post read-back +00:10 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cache absence does not hide cancel failure +00:10 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS remaining ID and failed post query are unconfirmed cancellations +00:10 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit observation rejects unavailable, malformed and unrelated IDs +00:10 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit already absent is idempotent; present cancel requires fresh absence +00:11 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:11 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: platform cleanup failure is visible and retryable, without raw content +00:11 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: DB-unavailable cleanup removes legacy raw and does not touch unrelated data +00:11 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: localizedNotificationText selects Korean text only for Korean locale +00:11 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: isScheduleAlarmPayload detects native alarm payloads without suppressing schedule notifications +00:11 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule notification payload to the schedule start screen +00:11 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes five-minute prompts as early-start schedule starts +00:11 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule and preparation updates to the alarm screen +00:11 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString ignores null, invalid, and unrelated payloads +00:11 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: invalid schedule hints never route to nearest preparation +00:11 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForData routes decoded local payload data with the same rules +00:11 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:11 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:11 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:12 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:12 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: planning snapshot cannot combine old schedule with new preparation +00:12 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: file database close/reopen with new gate verifies committed receipt +00:12 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:12 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: template and definition trigger versions wake actual dependent schedule watch +00:12 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:12 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:12 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:12 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:13 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:13 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:13 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk changes preserve only overridden fields and preparation +00:13 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: ending following leaves active and historical rows and their preparations intact +00:13 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: finite conflicts require explicit exclusion and excluded slots consume count +00:13 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: place edits never rename another occurrence or historical place +00:13 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk edit preserves active preparation and consumes its original count +00:13 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: stale first occurrence approval asks for review without changing durable data +00:13 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:13 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:13 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:13 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:13 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: an override cannot be moved into another occurrence preparation window +00:14 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:14 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:14 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:14 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:14 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: repeated initial creation cannot replace an existing profile +00:14 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: schedule fault rolls back the whole aggregate and same intent retries +00:14 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: schedule fault rolls back the whole aggregate and same intent retries +00:14 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:14 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: step fault rolls back the whole aggregate and same intent retries +00:14 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: concurrent finishes count distinct schedules once and duplicate only once +00:14 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: revision fault rolls back the whole aggregate and same intent retries +00:14 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back setting and profile changes +00:14 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: edit failure after old preparation deletion preserves old aggregate and revision +00:14 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:14 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: concurrent and recreated repository lost receipt retry commit once +00:14 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:14 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:14 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: semantic no-op preserves owned step IDs and revision +00:14 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: multiple preparation steps are a no-op even when caller omits links +00:14 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: raw revision update notifies DB watchers and failed profile write publishes no success +00:14 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: malformed new content metadata never drops registry ownership records +00:14 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: malformed new content metadata never drops registry ownership records +00:14 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: new store same revision and fresh memory generation rejects old draft +00:14 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: replaceAll persists records and removes the key when cleared +00:14 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: unrelated durable create conflict is explicit; reviewed new intent succeeds +00:14 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: unrelated durable create conflict is explicit; reviewed new intent succeeds +00:14 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: unrelated durable create conflict is explicit; reviewed new intent succeeds +00:14 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: unrelated durable create conflict is explicit; reviewed new intent succeeds +00:14 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: unrelated durable create conflict is explicit; reviewed new intent succeeds +00:14 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/alarm_registry_local_data_source_test.dart: preferences round trip preserves NotificationTiming.approximate and ownership pending=false +00:14 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: start between draft and commit conflicts and preserves protected fields +00:14 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: start between draft and commit conflicts and preserves protected fields +00:14 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: same shared place label edit copies ownership +00:14 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: invalid custom input is rejected atomically 0:00:00.000000 +00:14 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: invalid custom input is rejected atomically 0:01:59.000000 +00:14 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: create cannot inject outcome or frozen metadata +00:14 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring duplicate create has durable receipt and rejects changed input +00:14 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: workflow failed delivery retry does not re-save and coalesces pending intent +00:14 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: late delivery after replacement retains commit but never current success +00:14 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: custom to default to template preserves reference sources and owned cleanup +00:15 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: default fallback dependency ABA invalidates stale edit +00:15 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:15 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring recurring_schedule_segments fault rolls back definitions series materialization and revision +00:15 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring schedules fault rolls back definitions series materialization and revision +00:15 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring users fault rolls back definitions series materialization and revision +00:15 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: following split keeps stable root through clock reversal and recovers lost receipt +00:15 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: custom step 2 insertion failure has no partial aggregate +00:15 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:15 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: clock reaching preparation start protects unchanged database baseline +00:15 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedule date ranges as start-inclusive and end-exclusive +00:15 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.occurrence preparation_definition_steps failure preserves prior complete series +00:15 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:15 +338 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.occurrence users failure preserves prior complete series +00:15 +338 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule [E] + Expected: [ + ScheduleWithPlace:ScheduleWithPlace{schedule: Schedule(aggregateIncarnation: null, aggregateVersion: null, lastMutationId: null, lastMutationDigest: null, lastMutationVersion: null, id: schedule-2, placeId: place-2, scheduleName: Meeting schedule-2, timeZoneId: Asia/Seoul, occurrenceOffsetSeconds: null, scheduleTime: 2026-05-15 11:00:00.000, moveTime: 0:20:00.000000, isChanged: false, isStarted: false, scheduleSpareTime: 0:05:00.000000, scheduleNote: note, latenessTime: -1, doneStatus: notEnded, startedAt: null, finishedAt: null, preparationMode: null, preparationTemplateId: null, preparationTemplateName: null, preparationTemplateDeleted: false, preparationFrozen: false, scoreContributionRecorded: false, recurringSegmentId: null, recurringSlotKey: null, recurringOrdinal: null, recurringOverrides: , preparationDefinitionId: null), place: Place(id: place-2, placeName: Cafe)} + ] + Actual: [ + ScheduleWithPlace:ScheduleWithPlace{schedule: Schedule(aggregateIncarnation: 9205074e021ac754afa7e7c110b000e1, aggregateVersion: 0, lastMutationId: null, lastMutationDigest: null, lastMutationVersion: null, id: schedule-2, placeId: place-2, scheduleName: Meeting schedule-2, timeZoneId: Asia/Seoul, occurrenceOffsetSeconds: null, scheduleTime: 2026-05-15 11:00:00.000, moveTime: 0:20:00.000000, isChanged: false, isStarted: false, scheduleSpareTime: 0:05:00.000000, scheduleNote: note, latenessTime: -1, doneStatus: notEnded, startedAt: null, finishedAt: null, preparationMode: null, preparationTemplateId: null, preparationTemplateName: null, preparationTemplateDeleted: false, preparationFrozen: false, scoreContributionRecorded: false, recurringSegmentId: null, recurringSlotKey: null, recurringOrdinal: null, recurringOverrides: , preparationDefinitionId: null), place: Place(id: place-2, placeName: Cafe)} + ] + Which: at location [0] is ScheduleWithPlace: instead of ScheduleWithPlace: + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/data/data_sources/schedule_local_data_source_test.dart 161:5 main. + +00:15 +339 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.following preparation_definition_steps failure preserves prior complete series +00:15 +340 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.following preparation_definition_steps failure preserves prior complete series +00:15 +341 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.following users failure preserves prior complete series +00:15 +342 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recreated create receipt cannot validate old baseline even with reused mutation identity +00:15 +343 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: occurrence owned definition invalidates series root receipt dependency +00:16 +344 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:16 +345 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:16 +346 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:16 +347 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:16 +348 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:16 +349 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:16 +350 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:16 +351 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +352 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:16 +353 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: deletePreparation rejects empty preparation entities +00:16 +354 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: saves and loads the early start timestamp per schedule +00:16 +355 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: returns null for missing, corrupt, and incomplete session payloads +00:16 +356 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: clearSession removes only the requested schedule session +00:16 +357 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers map schedules to and from database rows preserving stored fields +00:16 +358 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps users to and from database rows preserving profile values +00:16 +359 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers empty users cannot be converted to database rows +00:16 +360 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps places and preparation steps to database rows +00:16 +361 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:16 +362 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getUserById returns null for an unknown user +00:16 +363 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getAllUsers returns all persisted users as domain entities +00:17 +364 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +365 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +366 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +367 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +368 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +369 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +370 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +371 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +372 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +373 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +374 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +375 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +376 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +377 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +378 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +379 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +380 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +381 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +382 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +383 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +384 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:17 +385 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: getPreparationUsersByUserId should return ordered preparation steps for a given user +00:17 +386 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:17 +387 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:17 +388 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: deletePreparationUser should delete a preparation step and relink the list +00:17 +389 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +390 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +391 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +392 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +393 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +394 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +395 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +396 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +397 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +398 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +399 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:17 +400 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:17 +401 -1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:17 +401 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:17 +401 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database [E] + Expected: Schedule: + Actual: Schedule: + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/data/daos/schedule_dao_test.dart 96:7 main.. + +00:17 +402 -2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:17 +402 -3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:17 +402 -3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getScheduleById should return a schedule from the database of given [id] [E] + Expected: ScheduleWithPlace: + Actual: ScheduleWithPlace: + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/data/daos/schedule_dao_test.dart 136:7 main.. + +00:17 +403 -3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:17 +404 -3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:17 +405 -3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:17 +406 -3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] +00:17 +407 -3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] +00:17 +407 -4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:17 +407 -4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] [E] + Expected: [ + ScheduleWithPlace:ScheduleWithPlace{schedule: Schedule(aggregateIncarnation: null, aggregateVersion: null, lastMutationId: null, lastMutationDigest: null, lastMutationVersion: null, id: 01a0d184-fc65-74d7-a5d8-fd3dca49b6a2, placeId: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, scheduleName: Test Schedule, timeZoneId: Asia/Seoul, occurrenceOffsetSeconds: null, scheduleTime: 2022-01-01 12:00:00.000, moveTime: 0:10:00.000000, isChanged: false, isStarted: false, scheduleSpareTime: 0:05:00.000000, scheduleNote: Test Note, latenessTime: 0, doneStatus: notEnded, startedAt: null, finishedAt: null, preparationMode: null, preparationTemplateId: null, preparationTemplateName: null, preparationTemplateDeleted: false, preparationFrozen: false, scoreContributionRecorded: false, recurringSegmentId: null, recurringSlotKey: null, recurringOrdinal: null, recurringOverrides: , preparationDefinitionId: null), place: Place(id: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, placeName: Test Place)} + ] + Actual: [ + ScheduleWithPlace:ScheduleWithPlace{schedule: Schedule(aggregateIncarnation: 294751197942d269f1b7d7d1f8f0ba49, aggregateVersion: 0, lastMutationId: null, lastMutationDigest: null, lastMutationVersion: null, id: 01a0d184-fc65-74d7-a5d8-fd3dca49b6a2, placeId: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, scheduleName: Test Schedule, timeZoneId: Asia/Seoul, occurrenceOffsetSeconds: null, scheduleTime: 2022-01-01 12:00:00.000, moveTime: 0:10:00.000000, isChanged: false, isStarted: false, scheduleSpareTime: 0:05:00.000000, scheduleNote: Test Note, latenessTime: 0, doneStatus: notEnded, startedAt: null, finishedAt: null, preparationMode: null, preparationTemplateId: null, preparationTemplateName: null, preparationTemplateDeleted: false, preparationFrozen: false, scoreContributionRecorded: false, recurringSegmentId: null, recurringSlotKey: null, recurringOrdinal: null, recurringOverrides: , preparationDefinitionId: null), place: Place(id: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, placeName: Test Place)} + ] + Which: at location [0] is ScheduleWithPlace: instead of ScheduleWithPlace: + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/data/daos/schedule_dao_test.dart 202:9 main.. + +00:17 +408 -4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:17 +409 -4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null +00:17 +410 -4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null +00:17 +410 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:17 +411 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:17 +411 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null [E] + Expected: [ + ScheduleWithPlace:ScheduleWithPlace{schedule: Schedule(aggregateIncarnation: null, aggregateVersion: null, lastMutationId: null, lastMutationDigest: null, lastMutationVersion: null, id: 01a0d184-fc65-74d7-a5d8-fd3dca49b6a2, placeId: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, scheduleName: Test Schedule, timeZoneId: Asia/Seoul, occurrenceOffsetSeconds: null, scheduleTime: 2022-01-01 12:00:00.000, moveTime: 0:10:00.000000, isChanged: false, isStarted: false, scheduleSpareTime: 0:05:00.000000, scheduleNote: Test Note, latenessTime: 0, doneStatus: notEnded, startedAt: null, finishedAt: null, preparationMode: null, preparationTemplateId: null, preparationTemplateName: null, preparationTemplateDeleted: false, preparationFrozen: false, scoreContributionRecorded: false, recurringSegmentId: null, recurringSlotKey: null, recurringOrdinal: null, recurringOverrides: , preparationDefinitionId: null), place: Place(id: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, placeName: Test Place)}, + ScheduleWithPlace:ScheduleWithPlace{schedule: Schedule(aggregateIncarnation: null, aggregateVersion: null, lastMutationId: null, lastMutationDigest: null, lastMutationVersion: null, id: 01a0d184-fd5b-7e67-86a2-84a2612c2729, placeId: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, scheduleName: Test Schedule, timeZoneId: Asia/Seoul, occurrenceOffsetSeconds: null, scheduleTime: 2022-01-04 00:00:00.000, moveTime: 0:10:00.000000, isChanged: false, isStarted: false, scheduleSpareTime: 0:05:00.000000, scheduleNote: Test Note, latenessTime: 0, doneStatus: notEnded, startedAt: null, finishedAt: null, preparationMode: null, preparationTemplateId: null, preparationTemplateName: null, preparationTemplateDeleted: false, preparationFrozen: false, scoreContributionRecorded: false, recurringSegmentId: null, recurringSlotKey: null, recurringOrdinal: null, recurringOverrides: , preparationDefinitionId: null), place: Place(id: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, placeName: Test Place)} + ] + Actual: [ + ScheduleWithPlace:ScheduleWithPlace{schedule: Schedule(aggregateIncarnation: 48c372587caf8f44e64a1d6ebadbde2b, aggregateVersion: 0, lastMutationId: null, lastMutationDigest: null, lastMutationVersion: null, id: 01a0d184-fc65-74d7-a5d8-fd3dca49b6a2, placeId: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, scheduleName: Test Schedule, timeZoneId: Asia/Seoul, occurrenceOffsetSeconds: null, scheduleTime: 2022-01-01 12:00:00.000, moveTime: 0:10:00.000000, isChanged: false, isStarted: false, scheduleSpareTime: 0:05:00.000000, scheduleNote: Test Note, latenessTime: 0, doneStatus: notEnded, startedAt: null, finishedAt: null, preparationMode: null, preparationTemplateId: null, preparationTemplateName: null, preparationTemplateDeleted: false, preparationFrozen: false, scoreContributionRecorded: false, recurringSegmentId: null, recurringSlotKey: null, recurringOrdinal: null, recurringOverrides: , preparationDefinitionId: null), place: Place(id: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, placeName: Test Place)}, + ScheduleWithPlace:ScheduleWithPlace{schedule: Schedule(aggregateIncarnation: 7eefbb332dd6730a29b76b13196a25e2, aggregateVersion: 0, lastMutationId: null, lastMutationDigest: null, lastMutationVersion: null, id: 01a0d184-fd5b-7e67-86a2-84a2612c2729, placeId: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, scheduleName: Test Schedule, timeZoneId: Asia/Seoul, occurrenceOffsetSeconds: null, scheduleTime: 2022-01-04 00:00:00.000, moveTime: 0:10:00.000000, isChanged: false, isStarted: false, scheduleSpareTime: 0:05:00.000000, scheduleNote: Test Note, latenessTime: 0, doneStatus: notEnded, startedAt: null, finishedAt: null, preparationMode: null, preparationTemplateId: null, preparationTemplateName: null, preparationTemplateDeleted: false, preparationFrozen: false, scoreContributionRecorded: false, recurringSegmentId: null, recurringSlotKey: null, recurringOrdinal: null, recurringOverrides: , preparationDefinitionId: null), place: Place(id: 01a0d184-fc68-7162-91a0-a8d3998f0d0f, placeName: Test Place)} + ] + Which: at location [0] is ScheduleWithPlace: instead of ScheduleWithPlace: + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/data/daos/schedule_dao_test.dart 234:9 main.. + +00:17 +412 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:18 +413 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:18 +414 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:18 +415 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:18 +416 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:18 +417 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:18 +418 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:18 +419 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:18 +420 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:18 +421 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:18 +422 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:18 +423 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:18 +424 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:18 +425 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:18 +426 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:18 +427 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:18 +428 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:18 +429 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:18 +430 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:18 +431 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:18 +432 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:18 +433 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:18 +434 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:18 +435 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:18 +436 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:18 +437 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:18 +438 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:18 +439 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:18 +440 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:18 +441 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:18 +442 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:18 +443 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:18 +444 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:18 +445 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:18 +446 -5: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart +00:19 +446 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:19 +447 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:19 +448 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: deleted and finished schedules accept reconciliation before awaiting cancellation +00:19 +449 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: reconciliation failures remain best-effort +00:19 +450 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:19 +451 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:19 +452 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:19 +453 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:19 +454 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:19 +455 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:19 +456 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:19 +457 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:19 +458 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:19 +459 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:19 +460 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: pending callers share failure, then retry the same instance and timestamp +00:19 +461 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: snapshot failure is partial and retry preserves a newer skip projection +00:19 +462 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: replacement generation does not reuse an old flight or write old runtime +00:19 +463 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: marker failure keeps durable run and a bounded retry preserves its clock +00:19 +464 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish while start cleanup is pending cannot resurrect the run +00:19 +465 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: dispose detaches the shared listener without releasing in-flight ownership +00:19 +466 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:19 +467 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure does not undo committed start or skip its snapshot +00:19 +468 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure after finish still clears completed runtime +00:19 +469 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:19 +470 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:19 +471 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:19 +472 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:19 +473 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:19 +474 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:19 +475 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:19 +476 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:20 +477 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +478 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +479 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +480 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +481 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +482 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +483 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +484 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +485 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +486 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +487 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +488 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +489 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +490 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +491 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +492 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +493 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +494 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: replacement generation invalidates preview with no additional effects +00:21 +495 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:21 +496 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: edit while cleanup awaits rejects transaction and exposes pending recovery +00:21 +497 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:21 +498 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:22 +499 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:22 +500 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +501 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +502 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +503 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +504 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +505 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +506 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:22 +507 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: replacement waits for the actual late native Future before same-ID reuse +00:22 +508 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +509 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +510 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +511 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +512 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +513 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +514 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +515 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:22 +516 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:23 +517 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:23 +518 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: off retains a cleanup warning and denial never becomes precise +00:23 +519 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: no future registrations never becomes precise from capability +00:23 +520 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: iOS alarm and notification language remains capability appropriate +00:23 +521 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:23 +522 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=false +00:23 +523 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:23 +524 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android reapplication preserves an existing owned platform ID +00:23 +525 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: a registry-owned ID with a conflicting route is cancelled before repair +00:23 +526 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:23 +527 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:23 +528 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:23 +529 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=false +00:23 +530 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=false +00:23 +531 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=false +00:23 +532 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native revoke and grant switch providers after cancelling the old registration +00:23 +533 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:23 +534 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:23 +535 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:23 +536 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:23 +537 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:23 +538 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:23 +539 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:23 +540 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: confirmed fallback orphans are targeted; unrelated and unknown ownership survive +00:23 +541 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:23 +542 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native failure uses only the actual fallback receipt after native cleanup +00:23 +543 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed native write cleanup forbids fallback and keeps cancellation identity +00:23 +544 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:23 +545 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:23 +546 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:23 +547 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:23 +548 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:24 +549 -5: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart +00:24 +549 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:24 +550 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +551 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +552 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +553 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +554 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +555 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +556 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +557 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +558 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +559 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +560 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +561 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +562 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +563 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleState timing helper durationUntilPreparationStartAt uses injected now deterministically +00:24 +564 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:24 +565 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:24 +566 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:24 +567 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: minimal progress hydrates names/durations from current source only +00:24 +568 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy exact fingerprint additionally requires all step fields +00:24 +569 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy launch becomes a minimal versioned route, never a start command +00:24 +570 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:24 +571 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:24 +572 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:24 +573 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:24 +574 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:25 +575 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: (setUpAll) +00:25 +575 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +576 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +577 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +578 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +579 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +580 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +581 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +582 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +583 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +584 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +585 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +586 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +587 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +588 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:26 +589 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:26 +590 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:26 +591 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:26 +592 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:26 +593 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:26 +594 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:26 +595 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:26 +596 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:26 +597 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:27 +598 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: header arrows move between months and request month loads +00:27 +599 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits without scroll at 1.3 text scale +00:27 +600 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: unbounded vertical layout keeps the configured row height +00:27 +601 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: HomeScreenTmp subscribes monthly bloc for today +00:27 +602 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits when today schedule exists +00:27 +603 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: regular portrait home matches Figma hero and card geometry +00:27 +604 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: banner clears the device safe area before rendering +00:27 +605 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: today schedule tile truncates long schedule names +00:27 +606 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: does not reopen schedule start on started-state ticks +00:27 +607 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: view calendar button navigates to calendar screen +00:27 +608 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +609 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +610 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +611 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +612 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +613 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +614 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +615 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +616 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +617 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +618 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +619 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +620 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +621 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +622 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +623 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +624 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +625 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +626 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +627 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +628 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +629 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:28 +630 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +631 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +632 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +633 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +634 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +635 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +636 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +637 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +638 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +639 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +640 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +641 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +642 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +643 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +644 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +645 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +646 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +647 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +648 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:28 +649 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden for early-started schedule +00:28 +650 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:29 +651 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:29 +652 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: delete action is hidden for finished schedules +00:29 +653 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/cubit/notification_gate_cubit_test.dart: authorized permission initializes notifications and allows app entry +00:29 +654 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:29 +655 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:29 +656 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:29 +657 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:29 +658 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:29 +659 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:29 +660 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:29 +661 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:29 +662 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:30 +663 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +664 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +665 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +666 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +667 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +668 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +669 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +670 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +671 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +672 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +673 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +674 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +675 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +676 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +677 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +678 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +679 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +680 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +681 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +682 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:30 +683 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +684 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +685 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +686 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +687 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +688 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +689 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +690 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +691 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +692 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +693 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +694 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +695 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow at preparation start boundary it transitions to started and navigates once +00:30 +696 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +697 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +698 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +699 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +700 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +701 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +702 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +703 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +704 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +705 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +706 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +707 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +708 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +709 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +710 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +711 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +712 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +713 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +714 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: saved create sheet refreshes the selected calendar date +00:30 +715 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: occurrences sharing step IDs have independent attempts +00:30 +716 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: occurrences sharing step IDs have independent attempts +00:30 +717 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:30 +718 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:30 +719 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:30 +720 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:30 +721 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:30 +722 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: android system back from calendar returns to home +00:30 +723 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: android system back from calendar returns to home +00:30 +724 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: android system back from calendar returns to home +00:30 +725 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: android system back from calendar returns to home +00:30 +726 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:31 +727 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:31 +728 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:31 +729 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:31 +730 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:31 +731 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:31 +732 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:32 +733 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:32 +734 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:32 +735 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/constants/app_colors_test.dart: white opacity colors keep the documented alpha ladder +00:32 +736 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/constants/app_colors_test.dart: white opacity colors keep the documented alpha ladder +00:32 +737 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/constants/app_colors_test.dart: white opacity colors keep the documented alpha ladder +00:32 +738 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow confirmation-required run stays stopped after prompt until explicit start +00:32 +739 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow confirmation-required run stays stopped after prompt until explicit start +00:32 +740 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/constants/app_colors_test.dart: social type string conversion is normalized for persistence +00:32 +741 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow mismatched fingerprint invalidates cached progress +00:32 +742 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start when early session and snapshot exist +00:32 +743 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start with only early session derives elapsed from session start +00:32 +744 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt loads schedule details and arms the prompt +00:32 +745 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects ended schedules and clears their alarm +00:32 +746 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects stale fingerprint unless it starts prep +00:32 +747 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm prompt validation failure clears only non-start prompts +00:32 +748 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish failure keeps the active schedule visible +00:32 +749 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: schedule events expose equality props for bloc deduping +00:32 +750 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatTime describes elapsed seconds in Korean units +00:32 +751 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatTimeTimer uses timer display with hours when needed +00:32 +752 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatEalyLateTime reports absolute early or late minutes +00:32 +753 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatElapsedTime keeps seconds two-digit padded +00:32 +754 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:33 +755 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default marker when a scheduled day is not selected +00:33 +756 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default empty-day behavior when there are no events +00:33 +757 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:34 +758 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:34 +759 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:34 +760 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:34 +761 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:34 +762 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:34 +763 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:34 +764 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:34 +765 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:34 +766 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:34 +767 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:34 +768 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:34 +769 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:34 +770 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:34 +771 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: date picker saves the initial date value +00:34 +772 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: date picker saves the initial date value +00:35 +773 -5: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart +00:35 +773 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (setUpAll) +00:35 +773 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/tile_style_test.dart: copyWith keeps existing tile style values when not overridden +00:35 +774 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: button styles resolve enabled and disabled visual contracts +00:35 +775 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: button styles resolve enabled and disabled visual contracts +00:35 +776 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme builds app calendar tokens and copies overrides +00:35 +777 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:36 +778 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:36 +779 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:36 +780 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:36 +781 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (tearDownAll) +00:36 +781 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: (setUpAll) +00:36 +781 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +782 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +783 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +784 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +785 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +786 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +787 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +788 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +789 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +790 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +791 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +792 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +793 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +794 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +795 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:37 +796 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:37 +797 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:37 +798 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +799 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +800 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +801 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +802 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +803 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:38 +804 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:38 +805 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:38 +806 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:38 +807 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old old at 390.0 +00:39 +808 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:39 +809 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:39 +810 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:39 +811 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:39 +812 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:39 +813 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:39 +814 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:39 +815 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:39 +816 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:40 +817 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:40 +818 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:40 +819 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:40 +820 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: only completed file save announces success +00:40 +821 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +822 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +823 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +824 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +825 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +826 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +827 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +828 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +829 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +830 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +831 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +832 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +833 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +834 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +835 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:40 +836 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +837 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +838 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +839 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +840 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +841 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +842 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +843 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +844 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +845 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +846 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +847 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:41 +848 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: local data and bundled privacy destinations remain in-app +00:42 +849 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:42 +850 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:42 +851 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:42 +852 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:42 +853 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:42 +854 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:42 +855 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:43 +856 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:43 +857 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +858 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +859 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +860 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +861 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +862 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +863 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +864 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +865 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +866 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:43 +867 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +868 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +869 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +870 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +871 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +872 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +873 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +874 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +875 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +876 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +877 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +878 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +879 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +880 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +881 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +882 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +883 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +884 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +885 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +886 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:44 +887 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:45 +888 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:45 +889 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:45 +890 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:45 +891 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:45 +892 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:45 +893 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:45 +894 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:45 +895 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:46 +896 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:47 +897 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:47 +898 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:47 +899 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:47 +900 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:47 +901 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:47 +902 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:48 +903 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:48 +904 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:48 +905 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:48 +906 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:48 +907 -5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +The following TimeoutException was thrown while running async test code: +TimeoutException after 0:00:05.000000: Future not completed + +When the exception was thrown, this was the stack: + + +#2 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart:133:9) + +#3 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:15) + +#4 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1952:5) + + +(elided 3 frames from dart:async and package:stack_trace) + +The exception was caught asynchronously. +════════════════════════════════════════════════════════════════════════════════════════════════════ +00:48 +907 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:48 +907 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates [E] + Test failed. See exception logs above. + The test description was: real button awaits durable commit; failure retries and stale route=false never navigates + +00:49 +908 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:49 +909 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/refreshed_screens_test.dart: secondary recurrence states fit a small phone with large text +00:49 +910 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +911 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +912 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +913 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +914 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +915 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +916 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +917 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +918 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +919 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +920 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:49 +921 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:49 +922 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:50 +923 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:50 +924 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:50 +925 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:50 +926 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +927 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +928 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +929 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +930 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +931 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +932 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +933 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +934 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +935 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +936 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +937 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +938 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +939 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +940 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +941 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +942 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +943 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +944 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +945 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +946 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +947 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +948 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +949 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +950 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +951 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:51 +952 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:52 +953 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:52 +954 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:52 +955 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:52 +956 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:52 +957 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +958 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +959 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +960 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +961 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +962 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +963 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +964 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +965 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +966 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +967 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +968 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:53 +969 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:54 +970 -6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +The following TimeoutException was thrown while running async test code: +TimeoutException after 0:00:05.000000: Future not completed + +When the exception was thrown, this was the stack: + + +#2 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart:133:9) + +#3 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:15) + +#4 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1952:5) + + +(elided 3 frames from dart:async and package:stack_trace) + +The exception was caught asynchronously. +════════════════════════════════════════════════════════════════════════════════════════════════════ +00:54 +970 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:54 +970 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates [E] + Test failed. See exception logs above. + The test description was: real button awaits durable commit; failure retries and stale route=true never navigates + +00:54 +971 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: formats selected date with Korean locale +00:54 +972 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:54 +973 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:54 +974 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:54 +975 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:54 +976 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet stays open and shows error when submit fails +00:55 +977 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edited name and place are submitted from form steps +00:55 +978 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edited name and place are submitted from form steps +00:55 +979 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: date time overlap keeps next disabled +00:55 +980 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: date time overlap keeps next disabled +00:55 +981 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: date time overlap keeps next disabled +00:55 +982 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:56 +983 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:56 +984 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_create_list_test.dart: create list forwards edits, reorder changes, and create request +00:56 +985 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_step_form_cubit_test.dart: name and time edits update validity for a preparation step draft +00:56 +986 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_step_form_cubit_test.dart: saving a step sends the current draft to the preparation form bloc +00:57 +987 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_state_event_test.dart: PreparationFormEvent props capture step edits +00:57 +988 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_state_event_test.dart: PreparationFormState orders linked entity steps for editing +00:57 +989 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_state_event_test.dart: PreparationFormState validates fields and converts visible steps +00:57 +990 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:57 +991 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:57 +992 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:57 +993 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +994 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +995 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +996 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +997 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +998 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +999 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +1000 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +1001 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +1002 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:58 +1003 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:58 +1004 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation errors explain invalid name and preparation time +00:58 +1005 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +00:58 +1006 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +00:58 +1007 -7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +00:58 +1008 -7: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getScheduleById should return a schedule from the database of given [id] + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database + ... and 3 more diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-full-coverage.log b/plans/audit-2026-09-23/artifacts/a08/a08-full-coverage.log new file mode 100644 index 00000000..87813c4d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-full-coverage.log @@ -0,0 +1,1059 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure source and test directories do not use .dart suffixes +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/repository_structure_test.dart: repository structure schedule place moving time imports use the folder path +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_data_operation_gate_test.dart: rejects competing operations instead of queueing destructive work +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_data_operation_gate_test.dart: failure releases lease; invalidated database cannot be reused +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_data_operation_gate_test.dart: exclusive validation does not invalidate writers until accepted +00:01 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: legacy reset marker with no ownership is never an empty success +00:01 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: corrupt registry uncertainty survives preference deletion and restart +00:01 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unknown ownership clears only after an app-wide provider receipt +00:01 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: ordinary ownership does not authorize reset or remove user data +00:01 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: returned cancel failure permits content deletion and survives fresh-instance retry +00:01 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 1 upgrades atomically and identities survive reopen/upsert +WARNING (drift): It looks like you've created the database class AppDatabase multiple times. When these two databases use the same QueryExecutor, race conditions will occur and might corrupt the database. +Try to follow the advice at https://drift.simonbinder.eu/faq/#using-the-database or, if you know what you're doing, set driftRuntimeOptions.dontWarnAboutMultipleDatabases = true +Here is the stacktrace from when the database was opened a second time: +#0 GeneratedDatabase._handleInstantiated (package:drift/src/runtime/api/db_base.dart:96:30) +#1 GeneratedDatabase._whenConstructed (package:drift/src/runtime/api/db_base.dart:73:12) +#2 new GeneratedDatabase (package:drift/src/runtime/api/db_base.dart:64:5) +#3 new _$AppDatabase (package:on_time_front/core/database/database.g.dart:6499:36) +#4 new AppDatabase.forTesting (package:on_time_front/core/database/database.dart) +#5 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart:105:37) + +#6 Declarer.test.. (package:test_api/src/backend/declarer.dart:253:15) + +#7 Declarer.test. (package:test_api/src/backend/declarer.dart:250:11) + +#8 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:318:9) + +This warning will only appear on debug builds. +00:01 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart: frozen schema 2 upgrades atomically and identities survive reopen/upsert +WARNING (drift): It looks like you've created the database class AppDatabase multiple times. When these two databases use the same QueryExecutor, race conditions will occur and might corrupt the database. +Try to follow the advice at https://drift.simonbinder.eu/faq/#using-the-database or, if you know what you're doing, set driftRuntimeOptions.dontWarnAboutMultipleDatabases = true +Here is the stacktrace from when the database was opened a second time: +#0 GeneratedDatabase._handleInstantiated (package:drift/src/runtime/api/db_base.dart:96:30) +#1 GeneratedDatabase._whenConstructed (package:drift/src/runtime/api/db_base.dart:73:12) +#2 new GeneratedDatabase (package:drift/src/runtime/api/db_base.dart:64:5) +#3 new _$AppDatabase (package:on_time_front/core/database/database.g.dart:6499:36) +#4 new AppDatabase.forTesting (package:on_time_front/core/database/database.dart) +#5 main. (file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/schedule_aggregate_migration_test.dart:105:37) + +#6 Declarer.test.. (package:test_api/src/backend/declarer.dart:253:15) + +#7 Declarer.test. (package:test_api/src/backend/declarer.dart:250:11) + +#8 Invoker._waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:318:9) + +This warning will only appear on debug builds. +00:01 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:01 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore waits for late platform completion before replacing database +00:02 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/local_reset_protocol_test.dart: unverified intent write reports no completion and never deletes data +00:02 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: actual restore cancellation failure preserves original database and ownership +00:02 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_replacement_integration_test.dart: reset retains independent cancellation ownership after content deletion and retries only cleanup +00:02 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/database_index_test.dart: creates lookup indexes for schedule and preparation DAO predicates +00:02 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/database_index_test.dart: rejects in-place upgrades across the one-way local cutover +00:03 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: pending reset preserves its ownership and bypasses unrelated privacy cleanup +00:03 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: successful bootstrap does not remove runtime +00:03 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: failure before Auth attempts cleanup and preserves original failure +00:03 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/bootstrap_privacy_boundary_test.dart: cleanup failure preserves both failures without private message text +00:03 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in recovers fresh owner without resetting user data +00:03 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: partial JSON in .pending recovers fresh owner without resetting user data +00:03 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/recurring_migration_test.dart: version 1 database upgrades in place preserving user outcomes and schedules +00:03 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS fallbackError remains partial without guessing ownership +00:03 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: iOS unownedNotification remains partial without guessing ownership +00:03 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: secure reset marker resumes deletion but Android uncertainty remains +00:03 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: secure reset marker resumes deletion but Android uncertainty remains +00:03 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: secure reset marker resumes deletion but Android uncertainty remains +00:03 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/time/civil_time_resolver_test.dart: fall-back overlap exposes first and second occurrence +00:03 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: explicit reset after damage cleans quarantine only after complete receipt +00:03 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":2} +00:03 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"complete","completed":[],"ownership":[]} +00:03 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: future version or impossible supported state is never automatically replaced: {"version":1,"reset":"none","completed":[],"ownership":[{"provider":"localNotification","scheduleId":"a","id":1,"pending":true},{"provider":"localNotification","scheduleId":"b","id":1,"pending":true}]} +00:03 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: read I/O failure does not quarantine or authorize deletion +00:03 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at recoveryMarked recovers through a new owner +00:03 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at quarantinedMain recovers through a new owner +00:03 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at written recovers through a new owner +00:03 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at verified recovers through a new owner +00:03 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at renamed recovers through a new owner +00:03 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/database/alarm_journal_recovery_test.dart: reconstruction interrupted at readBack recovers through a new owner +00:03 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +63: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:03 +64: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +65: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +66: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +67: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +68: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +69: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +70: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +71: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +72: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +73: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +74: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +75: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: round trip restores the authenticated plaintext +00:04 +76: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: wrong password cannot produce backup contents +00:04 +77: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:04 +78: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:04 +79: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:04 +80: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:04 +81: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:04 +82: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_crypto_test.dart: authenticated encryption detects a modified container +00:04 +83: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: normalizes canonically equivalent passwords to the same value +00:04 +84: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: preserves case and surrounding spaces +00:04 +85: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_password_test.dart: rejects passwords outside the 15 to 128 code point boundary +00:05 +86: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: saved receipt marks only the captured revision despite an ordinary edit +00:05 +87: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: cancel preserves freshness and releases gate for a retry +00:05 +88: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:05 +89: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_stale_contract_test.dart: preview followed by a durable edit never overwrites that edit +00:06 +90: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: second export, restore, and destructive operation reject while picker is open +00:06 +91: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: successful external save with missing profile reports metadata failure +00:06 +92: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/logging/app_logger_test.dart: AppLogger redaction redacts sensitive map values recursively +00:06 +93: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:06 +94: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:06 +95: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:06 +96: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:06 +97: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:06 +98: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:06 +99: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: late success cannot write freshness after lifecycle invalidation +00:06 +100: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_tap_router_test.dart: last valid tap survives delayed readiness and malformed input +00:06 +101: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: crypto failure releases gate without opening a destination +00:06 +102: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: crypto failure releases gate without opening a destination +00:06 +103: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: crypto failure releases gate without opening a destination +00:06 +104: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_tap_router_test.dart: duplicate native receipt blocks stale cold result without invalidating open A +00:06 +105: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +106: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +107: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +108: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +109: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +110: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +111: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +112: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +113: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +114: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: preview authenticates backup and restore replaces active data +00:06 +115: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: previous extension-only filter fails before reaching iOS host +00:06 +116: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:06 +117: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 2 restores owned preparation and exclusions without regenerating deleted slots +00:07 +118: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: cancel does not decrypt and a later valid selection can be previewed +00:07 +119: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +120: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +121: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +122: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +123: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +124: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +125: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: format 1 remains importable and malformed new references never replace data +00:07 +126: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:07 +127: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:07 +128: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:07 +129: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: truncated backup is rejected without writes +00:07 +130: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +131: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +132: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +133: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +134: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +135: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +136: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +137: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_service_test.dart: wrong password leaves current database unchanged +00:08 +138: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +139: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +140: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +141: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +142: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +143: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +144: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +145: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +146: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +147: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +148: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: wrong password is rejected without writes +00:08 +149: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/backup/backup_restore_picker_test.dart: read failure preserves data and does not prevent another attempt +00:08 +150: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: independent file retains only cancellation identity across reopen +00:08 +151: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at written reopens a complete committed generation +00:08 +152: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at verified reopens a complete committed generation +00:08 +153: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at renamed reopens a complete committed generation +00:08 +154: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: interrupted replacement at readBack reopens a complete committed generation +00:08 +155: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: staged first write is preserved and never reported as an empty installation +00:08 +156: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: truncated staged first write is preserved and blocks new ownership +00:08 +157: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {broken +00:08 +158: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {"version":2} +00:08 +159: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: malformed or unsupported committed journal stays intact: {"version":1,"ownership":[]} +00:08 +160: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: backup exclusion failure prevents creating ownership bytes +00:08 +161: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: only a fully completed reset journal can be removed +00:08 +162: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: duplicate actual platform IDs are rejected instead of hiding ownership +00:08 +163: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_ownership_journal_test.dart: impossible completion states cannot be written over a valid journal +00:09 +164: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: initializeLaunchHandling dispatches cold-start alarm payload +00:09 +165: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload dispatches resumed alarm payload +00:09 +166: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities maps native alarm provider capabilities +00:09 +167: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats missing platform implementation as unsupported +00:09 +168: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats native platform errors as unsupported +00:09 +169: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: getCapabilities treats null native response as unsupported +00:09 +170: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: checkPermission and requestPermission map native wire values +00:09 +171: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat missing platform implementation as unsupported +00:09 +172: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: permission checks treat native platform errors as unsupported +00:09 +173: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm sends the native alarm contract payload +00:09 +174: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm exposes permission failures as scheduling exception +00:09 +175: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: scheduleNativeAlarm maps unsupported and generic native errors +00:09 +176: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm sends cancellation payload for scheduled records +00:09 +177: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm reports missing native platform as unconfirmed +00:09 +178: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelNativeAlarm maps native platform failures to scheduling exception +00:09 +179: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: cancelAllNativeAlarms cancels every provided record in order +00:09 +180: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/alarm_scheduler_service_test.dart: dispatchPendingLaunchPayload ignores invalid and failed payload reads +00:09 +181: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=false snapshot after persistence +00:09 +182: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same ko detailed=true snapshot after persistence +00:09 +183: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=false snapshot after persistence +00:09 +184: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: native and fallback send the same en detailed=true snapshot after persistence +00:09 +185: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: legacy and corrupted content cannot bypass reconciliation at either provider +00:09 +186: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancel uses the plugin API for pending and presented IDs, never cancelAll +00:09 +187: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/scheduled_notification_delivery_test.dart: targeted cancellation errors are visible to reconciliation +00:09 +188: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/app_metadata_service_test.dart: package info metadata provider returns runtime app metadata +00:10 +189: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: owned legacy pending is cancelled without clearing step notifications +00:10 +190: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: cold launch collects once and concurrent initialization is single flight +00:10 +191: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: cold launch collects once and concurrent initialization is single flight +00:10 +192: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:10 +193: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:10 +194: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:10 +195: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_privacy_cleanup_test.dart: known schedule payload with invalid route ID cleans owned pending +00:10 +196: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: getter failure releases single flight and callback still works before retry +00:10 +197: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: getter failure releases single flight and callback still works before retry +00:10 +198: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback B arriving while cold A getter waits wins, raw legacy fields removed +00:10 +199: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: callback before delegate installation preserves only safe latest payload +00:10 +200: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: Noop to installed delegate cannot let slow cold A overwrite native B +00:10 +201: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_launch_test.dart: data replacement invalidates delayed initial launch +00:10 +202: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache observation preserves source and minimal ownership +00:10 +203: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android read failure is unknown, not absent +00:10 +204: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cancel needs public success and post read-back +00:10 +205: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android cache absence does not hide cancel failure +00:10 +206: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: Android remaining ID and failed post query are unconfirmed cancellations +00:10 +207: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS OS observation preserves source and minimal ownership +00:10 +208: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS read failure is unknown, not absent +00:10 +209: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cancel needs public success and post read-back +00:10 +210: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS cache absence does not hide cancel failure +00:10 +211: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: iOS remaining ID and failed post query are unconfirmed cancellations +00:10 +212: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit observation rejects unavailable, malformed and unrelated IDs +00:10 +213: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/delivery_observation_test.dart: AlarmKit already absent is idempotent; present cancel requires fresh absence +00:11 +214: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: all legacy runs migrate, orphans clear, unrelated settings survive +00:11 +215: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: platform cleanup failure is visible and retryable, without raw content +00:11 +216: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/runtime_privacy_migration_test.dart: DB-unavailable cleanup removes legacy raw and does not touch unrelated data +00:11 +217: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: localizedNotificationText selects Korean text only for Korean locale +00:11 +218: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: isScheduleAlarmPayload detects native alarm payloads without suppressing schedule notifications +00:11 +219: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule notification payload to the schedule start screen +00:11 +220: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes five-minute prompts as early-start schedule starts +00:11 +221: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString routes schedule and preparation updates to the alarm screen +00:11 +222: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForPayloadString ignores null, invalid, and unrelated payloads +00:11 +223: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: invalid schedule hints never route to nearest preparation +00:11 +224: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/notification_routing_test.dart: notificationRouteForData routes decoded local payload data with the same rules +00:11 +225: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/core/services/android_notification_timing_test.dart: fresh permission chooses exact then approximate without full-screen +00:11 +226: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +227: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +228: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +229: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +230: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +231: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +232: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +233: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: product source and release configuration contain no network client +00:11 +234: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: iOS remote notification hooks violate the local-only boundary +00:11 +235: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/local_only_boundary_test.dart: web remote bootstrap resources violate the local-only boundary +00:11 +236: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: edit snapshot cannot combine old schedule with new preparation +00:11 +237: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: planning snapshot cannot combine old schedule with new preparation +00:11 +238: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: file database close/reopen with new gate verifies committed receipt +00:11 +239: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real preparation watch sees committed aggregate and never rolled back value +00:11 +240: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: template and definition trigger versions wake actual dependent schedule watch +00:11 +241: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: new draft reads default preparation and revision in one real transaction +00:11 +242: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: following receipt survives file close reopen and deletion of original occurrence +00:11 +243: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:12 +244: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:12 +245: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:12 +246: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: series owns reusable preparation and direct duplicate create rejects missing intent receipt +00:12 +247: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: a failed revision write rolls back the schedule transition and retry succeeds +00:12 +248: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:13 +249: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: deleting one occurrence never refills the count after rematerialization +00:13 +250: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_start_transaction_test.dart: fresh session respects valid run vs explicit confirmation=false missing=false without overwriting durable first start +00:13 +251: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:13 +252: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:13 +253: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:13 +254: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: after three elapsed slots, two detached overrides leave five recurring and two standalone +00:13 +255: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:13 +256: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/alarm_registry_repository_impl_test.dart: real registry preserves duplicate ownership until confirmed cancellation: failure=false +00:13 +257: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:13 +258: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:13 +259: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:13 +260: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:13 +261: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:13 +262: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a detached occurrence and a later new slot on its original date remain distinct +00:13 +263: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk changes preserve only overridden fields and preparation +00:13 +264: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: ending following leaves active and historical rows and their preparations intact +00:13 +265: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: finite conflicts require explicit exclusion and excluded slots consume count +00:13 +266: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: place edits never rename another occurrence or historical place +00:13 +267: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: bulk edit preserves active preparation and consumes its original count +00:13 +268: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: stale first occurrence approval asks for review without changing durable data +00:13 +269: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: explicit count edit replaces the remaining budget +00:13 +270: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: a deleted slot remains consumed when the new weekdays omit that date +00:13 +271: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded series detects persistent rule collisions and stores only nearby occurrences +00:13 +272: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/recurring_schedule_repository_test.dart: unbounded daily intervals prove conflicts beyond deleted occurrences +00:13 +273: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:13 +274: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/local_schedule_score_test.dart: normal and late finishes contribute once while deletion preserves aggregate +00:14 +275: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +276: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +277: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +278: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +279: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +280: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +281: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +282: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +283: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +284: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +285: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +286: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +287: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: alarm repository returns preserved offset and detail preference from the DB +00:14 +288: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:14 +289: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: place fault rolls back the whole aggregate and same intent retries +00:14 +290: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: spare time preserves preferences, scores and export metadata +00:14 +291: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: schedule fault rolls back the whole aggregate and same intent retries +00:14 +292: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: schedule fault rolls back the whole aggregate and same intent retries +00:14 +293: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: same preferences and empty score reset are exact no-ops +00:14 +294: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: step fault rolls back the whole aggregate and same intent retries +00:14 +295: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: finish and spare time changes retain both results with one revision each +00:14 +296: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: revision fault rolls back the whole aggregate and same intent retries +00:14 +297: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: concurrent finishes count distinct schedules once and duplicate only once +00:14 +298: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: concurrent finishes count distinct schedules once and duplicate only once +00:14 +299: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: concurrent and recreated repository lost receipt retry commit once +00:14 +300: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: concurrent and recreated repository lost receipt retry commit once +00:14 +301: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:14 +302: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: revision failure rolls back schedule result and score together +00:14 +303: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: semantic no-op preserves owned step IDs and revision +00:14 +304: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: onboarding preparation and profile roll back if revision fails +00:14 +305: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +306: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +307: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: two edit drafts and value ABA conflict without using global revision +00:14 +308: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: empty profile starts without a durable revision and creation is race safe +00:14 +309: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: deleted create retry and recreated same id reject old intent +00:14 +310: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/profile_write_preservation_test.dart: raw revision update notifies DB watchers and failed profile write publishes no success +00:14 +311: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: new store same revision and fresh memory generation rejects old draft +00:14 +312: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: unrelated durable create conflict is explicit; reviewed new intent succeeds +00:14 +313: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: start between draft and commit conflicts and preserves protected fields +00:14 +314: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: same shared place label edit copies ownership +00:14 +315: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: invalid custom input is rejected atomically 0:00:00.000000 +00:14 +316: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: invalid custom input is rejected atomically 0:01:59.000000 +00:14 +317: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: create cannot inject outcome or frozen metadata +00:14 +318: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring duplicate create has durable receipt and rejects changed input +00:14 +319: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: workflow failed delivery retry does not re-save and coalesces pending intent +00:14 +320: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_data_source_contract_test.dart: schedule local data source contract does not expose domain entities +00:14 +321: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_data_source_contract_test.dart: schedule local data source contract does not expose domain entities +00:14 +322: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: custom to default to template preserves reference sources and owned cleanup +00:14 +323: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: default fallback dependency ABA invalidates stale edit +00:14 +324: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:15 +325: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +326: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +327: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +328: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +329: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:15 +330: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: semantic same following save does not split or increase revision +00:15 +331: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:15 +332: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: root receipt rejects dependency place mutation after following commit +00:15 +333: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: root receipt rejects dependency place mutation after following commit +00:15 +334: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:15 +335: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: custom step 0 insertion failure has no partial aggregate +00:15 +336: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: custom step 1 insertion failure has no partial aggregate +00:15 +337: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: custom step 2 insertion failure has no partial aggregate +00:15 +338: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: clock reaching preparation start protects unchanged database baseline +00:15 +339: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.occurrence preparation_definition_steps failure preserves prior complete series +00:15 +340: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.occurrence users failure preserves prior complete series +00:15 +341: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.following preparation_definition_steps failure preserves prior complete series +00:15 +342: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recurring RecurringEditScope.following users failure preserves prior complete series +00:15 +343: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: recreated create receipt cannot validate old baseline even with reused mutation identity +00:15 +344: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: occurrence owned definition invalidates series root receipt dependency +00:15 +345: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: legacy persisted row withPlace=false cannot rewind identity version or receipt +00:15 +346: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart: legacy persisted row withPlace=true cannot rewind identity version or receipt +00:15 +347: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists elapsed step state for a schedule +00:15 +348: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: savePreparation persists preparation run action events +00:15 +349: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation returns null for missing or corrupt snapshots +00:15 +350: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: loadPreparation rejects unverifiable legacy snapshots +00:15 +351: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: clearPreparation removes only the requested schedule snapshot +00:15 +352: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: explicit malformed events and negative progress invalidate whole snapshot +00:15 +353: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_with_time_local_data_source_test.dart: TimedPreparationRepositoryImpl delegates cache lifecycle operations +00:15 +354: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates and updates the default user preparation +00:16 +355: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/preparation_local_data_source_test.dart: creates, reads, updates, and deletes custom schedule preparation +00:16 +356: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: saves and loads the early start timestamp per schedule +00:16 +357: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: saves and loads the early start timestamp per schedule +00:16 +358: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: returns null for missing, corrupt, and incomplete session payloads +00:16 +359: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/early_start_session_local_data_source_test.dart: clearSession removes only the requested schedule session +00:16 +360: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers map schedules to and from database rows preserving stored fields +00:16 +361: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps users to and from database rows preserving profile values +00:16 +362: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers empty users cannot be converted to database rows +00:16 +363: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/mappers/domain_persistence_mappers_test.dart: domain persistence mappers maps places and preparation steps to database rows +00:16 +364: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: createUser persists the user profile fields +00:16 +365: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getUserById returns null for an unknown user +00:16 +366: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/user_dao_test.dart: getAllUsers returns all persisted users as domain entities +00:17 +367: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +368: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +369: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +370: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:17 +371: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +372: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +373: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +374: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +375: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +376: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: createPreparationUser should insert preparation steps and link them as a linked list +00:17 +377: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_user_dao_test.dart: getPreparationStepById should throw when the preparation step does not exist +00:17 +378: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:17 +379: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:17 +380: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:17 +381: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:17 +382: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:17 +383: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:17 +384: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:17 +385: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:17 +386: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:17 +387: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:17 +388: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: monthly 31st counts real dates and skips short months +00:17 +389: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:17 +390: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:17 +391: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:17 +392: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:17 +393: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fifth weekday skips missing months while last weekday does not +00:17 +394: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId orders three linked steps when rows are out of order +00:17 +395: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId orders three linked steps when rows are out of order +00:17 +396: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId orders three linked steps when rows are out of order +00:17 +397: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId orders three linked steps when rows are out of order +00:17 +398: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] +00:17 +399: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/recurrence/recurrence_engine_test.dart: fall overlap requires an explicit first or second choice +00:17 +400: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId keeps remaining steps when a link is broken +00:17 +401: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId keeps remaining steps when a link is broken +00:17 +402: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId keeps remaining steps when a link is broken +00:17 +403: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null +00:17 +404: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null +00:17 +405: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns each step once when links form a cycle +00:17 +406: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationSchedulesByScheduleId returns each step once when links form a cycle +00:17 +407: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById returns the stored step contract +00:17 +408: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations getPreparationStepById throws for a missing step +00:17 +409: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations updatePreparationSchedule changes name time and next pointer +00:17 +410: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/preparation_schedule_dao_test.dart: schedule preparations deletePreparationSchedule removes middle step and relinks neighbors +00:17 +411: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: Android grant and revoke replace timing while preserving capacity and ownership +00:17 +412: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: actual downgrade receipt is persisted instead of optimistic permission +00:17 +413: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy or malformed mode keeps ownership and forces Android replacement +00:17 +414: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: timing change cannot overwrite a failed cancellation +00:17 +415: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: display denial plus cancellation failure preserves OS ownership +00:17 +416: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: requests the full future window and schedules only eligible records +00:17 +417: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: skips a just-missed alarm instead of scheduling catch-up +00:17 +418: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: clears an already armed just-missed alarm instead of scheduling catch-up +00:17 +419: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: android schedule notifications arm beyond 24 hours +00:17 +420: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: arms only the nearest 60 future alarms and reports the overflow +00:17 +421: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps alarm content private unless detailed content is enabled +00:17 +422: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces an existing detailed registration when details are disabled +00:17 +423: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not report private armed or discard evidence when detailed cancellation fails +00:17 +424: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: changes detailed titles but does not reschedule private title-only edits +00:17 +425: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reconciles locale and visible time zone using one content snapshot +00:18 +426: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: legacy content metadata is replaced once and current persisted metadata is stable +00:18 +427: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancellation failure survives restart, blocks only that ID and retries after recovery +00:18 +428: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: private scheduling failure never restores details and retries next time +00:18 +429: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disable retains failed cancellation instead of claiming disabled +00:18 +430: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cleans a past owned notification without re-emitting it or canceling other IDs +00:18 +431: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: an already started preparation never receives a new start notification +00:18 +432: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: iOS native privacy cancellation also preserves failed evidence and retries +00:18 +433: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: overlapping requests each observe their requested pass +00:18 +434: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancels stale record before rescheduling changed fingerprint +00:18 +435: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: replaces matching Android native registry record with notification +00:18 +436: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: keeps matching iOS fallback only when OS pending confirms it +00:18 +437: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reschedules stale record with old alarm launch payload version +00:18 +438: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when native alarms are unsupported +00:18 +439: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: uses local notification fallback when exact alarm permission is denied +00:18 +440: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: falls back to local notification when native scheduling fails +00:18 +441: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports notification permission when only fallback delivery is denied +00:18 +442: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports permissionNeeded when exact alarm and fallback permissions are denied +00:18 +443: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: does not use notification fallback when fallback provider is disabled +00:18 +444: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: settingsUnavailable reports without canceling existing registry +00:18 +445: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: alarm window failure reports partial without throwing +00:18 +446: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: global disabled cancels all local alarms and clears registry +00:18 +447: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: reports partial when scheduling a desired notification fails +00:18 +448: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: notification permission is reported when fallback notification is unavailable +00:18 +449: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback scheduling failures become permission or partial reports +00:18 +450: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: fallback alarm scheduling exceptions without permission issue are partial failures +00:18 +451: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: unsupported local providers return unsupported without throwing +00:18 +452: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: permission check failures degrade to denied or unsupported states +00:18 +453: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/reconcile_alarms_use_case_test.dart: cancel failures retain retry evidence without claiming armed +00:18 +454: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: created and updated schedules reconcile without targeted cancellation +00:18 +455: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: deleted and finished schedules accept reconciliation before awaiting cancellation +00:18 +456: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_mutation_alarm_effects_coordinator_test.dart: reconciliation failures remain best-effort +00:18 +457: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/get_nearest_upcoming_schedule_use_case_test.dart: swallows background week load failures before streaming cache +00:18 +458: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/load_schedule_form_draft_use_case_test.dart: create draft uses default preparation, user spare time, generated ids, and seeded date +00:19 +459: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: SaveTimedPreparationUseCase stores a fingerprinted snapshot +00:19 +460: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation read and clear use cases delegate by schedule id +00:19 +461: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: early start session use cases mark read and clear one schedule +00:19 +462: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: timed preparation snapshots copy changed cache fields +00:19 +463: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: LoadAdjacentScheduleWithPreparationUseCase loads each schedule prep +00:19 +464: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetPreparationByScheduleIdUseCase waits for matching cache entry +00:19 +465: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: preparation use cases delegate to repository with explicit arguments +00:19 +466: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: default and stream preparation use cases expose repository values +00:19 +467: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/timed_preparation_use_cases_test.dart: GetNearestUpcomingScheduleUseCase loads prep for nearest active schedule +00:19 +468: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: pending callers share failure, then retry the same instance and timestamp +00:19 +469: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: snapshot failure is partial and retry preserves a newer skip projection +00:19 +470: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: replacement generation does not reuse an old flight or write old runtime +00:19 +471: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: marker failure keeps durable run and a bounded retry preserves its clock +00:19 +472: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish while start cleanup is pending cannot resurrect the run +00:19 +473: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: dispose detaches the shared listener without releasing in-flight ownership +00:19 +474: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: early start begins the schedule preparation session once and saves progress +00:19 +475: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure does not undo committed start or skip its snapshot +00:19 +476: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: cancellation failure after finish still clears completed runtime +00:19 +477: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: matching timed preparation snapshot restores elapsed session progress +00:19 +478: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: stale timed preparation snapshot clears persisted session state +00:19 +479: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: ended prompted schedule is rejected and scheduled delivery is cancelled +00:19 +480: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: valid prompted schedule loads preparation and returns ready schedule +00:19 +481: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: database failure retains both prompt kinds as unavailable +00:19 +482: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: BehaviorSubject replay resolves the loaded map and stream errors stay unavailable +00:19 +483: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: definitive missing is rejected without treating storage failure as missing +00:19 +484: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/schedule_preparation_session_use_case_test.dart: finish completes the preparation session and clears scheduled delivery +00:19 +485: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +486: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +487: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +488: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +489: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +490: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +491: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +492: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +493: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +494: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +495: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: preclaim durable edit rejects without cleanup or generation change +00:20 +496: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +497: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +498: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +499: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +500: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +501: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:20 +502: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual create/update entrypoints rerun after snapshot and retain nearest 60 without materialize loop +00:21 +503: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: edit while cleanup awaits rejects transaction and exposes pending recovery +00:21 +504: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: actual default and custom preparation commits request latest delivery timing +00:21 +505: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: same token shares full pending receipt and delivery retry never reapplies +00:21 +506: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: two unbounded series share the nearest 60 alarms and survive reopening +00:21 +507: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: failed actual replacement transaction preserves data and retry commits once +00:21 +508: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:21 +509: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:21 +510: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/recurring_alarm_integration_test.dart: deletion cancels one alarm, refills capacity and stays excluded +00:21 +511: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:21 +512: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:21 +513: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:21 +514: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: claim generation survives a later replacement before continuation +00:21 +515: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/alarm_reconciliation_concurrency_test.dart: failure alone stops; a request from completion starts another pass +00:21 +516: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:21 +517: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:21 +518: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:21 +519: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:21 +520: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:21 +521: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:21 +522: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:21 +523: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:21 +524: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/local_data_workflows_test.dart: old delayed retry cannot absorb a newer generation receipt +00:22 +525: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=true +00:22 +526: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: lost ownership is not reported as armed or disabled: enabled=false +00:22 +527: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: write-ahead identity precedes native scheduling and a now-expired target is not replayed +00:22 +528: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:22 +529: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:22 +530: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: only all successful exact receipts produce precise status +00:22 +531: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=true +00:22 +532: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=true +00:22 +533: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: no future registrations never becomes precise from capability +00:22 +534: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=true +00:22 +535: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/schedule_notification_status_test.dart: iOS alarm and notification language remains capability appropriate +00:22 +536: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native query failure stays incomplete enabled=false +00:22 +537: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: empty registry with native unmapped ID stays incomplete enabled=false +00:22 +538: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native revoke and grant switch providers after cancelling the old registration +00:22 +539: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: unconfirmed provider cancellation blocks duplicate fallback, then retry converges +00:22 +540: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS pending loss repairs a future native reservation but never replays an elapsed one +00:22 +541: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native query unknown retains ownership without treating absence or claiming success +00:22 +542: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: iOS notification OS loss repairs while successful query allows keeping the request +00:23 +543: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: Android cache presence causes same-ID reapplication rather than an OS survival claim +00:23 +544: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed post-schedule verification preserves ownership but excludes armed success +00:23 +545: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: OS query error after startup never becomes a healthy stored notification +00:23 +546: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: confirmed fallback orphans are targeted; unrelated and unknown ownership survive +00:23 +547: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: orphan cancellation failure persists its real platform ID and retries while disabled +00:23 +548: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: native failure uses only the actual fallback receipt after native cleanup +00:23 +549: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/use-cases/delivery_reconciliation_test.dart: failed native write cleanup forbids fallback and keeps cancellation identity +00:23 +550: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: local profile derives punctuality from eligible outcomes +00:23 +551: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/user_entity_test.dart: empty user exposes null convenience values +00:23 +552: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native grant uses the native alarm provider +00:23 +553: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with granted fallback keeps delivery enabled without a blocking issue +00:23 +554: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: native denial with denied fallback blocks delivery on native permission +00:23 +555: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: unsupported native alarms use granted fallback delivery +00:23 +556: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/alarm_delivery_policy_test.dart: all unavailable reports unsupported without disabling alarms +00:23 +557: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities orders linked preparation steps before timing conversion +00:23 +558: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities calculates left time before leave with explicit now +00:23 +559: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports late status after leave time has passed +00:23 +560: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities fast-forwards preparation steps for late entry +00:23 +561: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities single preparation step tracks elapsed time and diagnostics +00:23 +562: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities marks all steps done for very late entry inside schedule window +00:23 +563: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities reports display helpers for current and completed steps +00:23 +564: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities skipCurrentStep marks only the active step done +00:23 +565: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities completed and empty preparations expose safe display fallbacks +00:23 +566: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities cache fingerprint changes when preparation step name changes +00:23 +567: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities schedule with preparation derives start time and total duration +00:23 +568: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: Preparation timing entities combines a schedule entity with timed preparation preserving fields +00:23 +569: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleEntity diagnostics string representation includes schedule identity for diagnostics +00:23 +570: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_timing_entity_test.dart: ScheduleState timing helper durationUntilPreparationStartAt uses injected now deterministically +00:23 +571: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not import lower architectural layers +00:24 +572: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/domain_entity_boundary_test.dart: domain entities do not expose persistence mapper methods +00:24 +573: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: identity separates timing, ordered shape, content and delimiters +00:24 +574: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: every identity-affecting edit invalidates progress without mutating data +00:24 +575: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: minimal progress hydrates names/durations from current source only +00:24 +576: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy exact fingerprint additionally requires all step fields +00:24 +577: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/domain/entities/preparation_snapshot_validation_test.dart: legacy launch becomes a minimal versioned route, never a start command +00:24 +578: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns early-start scheduleStart target in upcoming when schedule exists +00:24 +579: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in ongoing when schedule exists +00:24 +580: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns alarmScreen target in started when schedule exists +00:24 +581: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null when no schedule exists +00:24 +582: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/utils/today_tile_navigation_test.dart: Today tile navigation resolver returns null in initial and notExists +00:25 +583: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: firstDayOfWeek resolves to Monday for the selected week +00:25 +584: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:25 +585: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:25 +586: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:25 +587: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:25 +588: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:25 +589: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:25 +590: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/week_calendar_test.dart: renders one week and reports the tapped date +00:25 +591: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +592: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +593: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +594: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +595: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +596: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +597: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +598: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +599: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:25 +600: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=false +00:26 +601: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: selecting a day reports the selected calendar date +00:26 +602: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:26 +603: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 portrait home fits actual font, schedule=true +00:26 +604: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/components/month_calendar_test.dart: compact height clamps calendar rows without overflow +00:26 +605: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:26 +606: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:26 +607: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: 430 home last calendar day and primary routes remain tappable +00:26 +608: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits without scroll at 1.3 text scale +00:26 +609: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: HomeScreenTmp subscribes monthly bloc for today +00:26 +610: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/home/screens/home_screen_tmp_test.dart: compact portrait home fits when today schedule exists +00:27 +611: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +612: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +613: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +614: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +615: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +616: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +617: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +618: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +619: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +620: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +621: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +622: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +623: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +624: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +625: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +626: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +627: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +628: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +629: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +630: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +631: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +632: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +633: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +634: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +635: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +636: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +637: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: expanded tile shows travel, preparation, spare in order +00:27 +638: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:27 +639: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:27 +640: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact future empty state fits with add button +00:27 +641: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: tile text updates when schedule fields change for same id +00:28 +642: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:28 +643: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: short past empty state fits without add button +00:28 +644: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/component/schedule_detail_test.dart: edit action is hidden after preparation start time +00:28 +645: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +646: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +647: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +648: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +649: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +650: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +651: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +652: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +653: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +654: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +655: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +656: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +657: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +658: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +659: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +660: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +661: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: compact selected day with schedules scrolls without overflow +00:28 +662: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: initial date before supported range is clamped to first month +00:28 +663: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: calendar header arrows move the visible month +00:28 +664: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:28 +665: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:28 +666: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:28 +667: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:28 +668: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:28 +669: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:28 +670: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: selecting another day updates the detail list for that day +00:29 +671: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:29 +672: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: actual periodic refresh submits a fresh paused step once +00:29 +673: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:29 +674: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: month load failures show calendar error state +00:29 +675: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/preparation_step_delivery_test.dart: AppLifecycleState.resumed updates UI silently without later replay +00:29 +676: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +677: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +678: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +679: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +680: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +681: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +682: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +683: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +684: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +685: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +686: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +687: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +688: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +689: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +690: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +691: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +692: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +693: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +694: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +695: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +696: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +697: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +698: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +699: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +700: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +701: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +702: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +703: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +704: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +705: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +706: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +707: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +708: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +709: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +710: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +711: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +712: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +713: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +714: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +715: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +716: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +717: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +718: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +719: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +720: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +721: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +722: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +723: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/calendar/screens/calendar_screen_test.dart: delete failure keeps calendar visible and shows error dialog +00:29 +724: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +725: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +726: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +727: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +728: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +729: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +730: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow new upcoming schedule cancels old timer and tracks latest schedule +00:29 +731: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow early start from upcoming emits started and marks early session +00:29 +732: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm launch start action keeps cached schedule on start prompt +00:29 +733: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm launch start action keeps stale-fingerprint cached schedule on start prompt +00:29 +734: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +735: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +736: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +737: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +738: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:30 +739: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:31 +740: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:31 +741: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow future upcoming schedule stops active preparation timer +00:31 +742: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:31 +743: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:31 +744: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:31 +745: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow official start timer does not push scheduleStart when already early-started +00:31 +746: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow same schedule re-emission preserves started state when early-started +00:31 +747: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish clears timed preparation and early session state +00:31 +748: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow confirmation-required run stays stopped after prompt until explicit start +00:31 +749: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow mismatched fingerprint invalidates cached progress +00:31 +750: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start when early session and snapshot exist +00:31 +751: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow resume before official start with only early session derives elapsed from session start +00:31 +752: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt loads schedule details and arms the prompt +00:31 +753: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects ended schedules and clears their alarm +00:31 +754: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow remote alarm prompt rejects stale fingerprint unless it starts prep +00:31 +755: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow alarm prompt validation failure clears only non-start prompts +00:31 +756: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: ScheduleBloc preparation runtime flow finish failure keeps the active schedule visible +00:31 +757: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/app/bloc/schedule/schedule_bloc_test.dart: schedule events expose equality props for bloc deduping +00:31 +758: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart +00:32 +758: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/utils/time_format_test.dart: formatTime describes elapsed seconds in Korean units +00:32 +759: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:32 +760: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:32 +761: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:32 +762: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: hides schedule marker when the day is selected +00:32 +763: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default marker when a scheduled day is not selected +00:32 +764: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/calendar/schedule_marker_builder_test.dart: uses the default empty-day behavior when there are no events +00:32 +765: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/custom_alert_dialog_test.dart: renders title, content, and actions with configured spacing +00:32 +766: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +767: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +768: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:32 +769: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:33 +770: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:33 +771: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:33 +772: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/two_button_delete_dialog_test.dart: delete dialog wrapper returns false on cancel +00:33 +773: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:33 +774: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker saves the selected duration and disposes +00:33 +775: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: minute picker cancel disposes without saving +00:33 +776: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/cupertino_picker_modal_test.dart: timer picker saves the initial timer duration +00:33 +777: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/arc_indicator_test.dart: ArcIndicator paints background and progress arcs without throwing +00:33 +778: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:33 +779: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: renders destructive fixed button text +00:33 +780: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/components/modal_wide_button_test.dart: flex layout works in row without overflow +00:34 +781: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (setUpAll) +00:34 +781: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: button styles resolve enabled and disabled visual contracts +00:34 +782: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme builds app calendar tokens and copies overrides +00:34 +783: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:34 +784: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:34 +785: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: calendar theme can be derived from build context +00:34 +786: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/theme/button_and_calendar_theme_test.dart: (tearDownAll) +00:34 +786: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route fades and glides into place +00:34 +787: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: standard app route exits back toward its entry edge +00:34 +788: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/app_route_transition_test.dart: bottom navigation routes exit back toward their entry edge +00:35 +789: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/route_arguments_test.dart: calendar route arguments prefers DateTime extra and falls back to query parameters +00:35 +790: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/route_arguments_test.dart: calendar route arguments router does not throw for malformed calendar extras +00:35 +791: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/route_arguments_test.dart: calendar route arguments router does not throw for malformed calendar extras +00:35 +792: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/route_arguments_test.dart: calendar route arguments router does not throw for malformed calendar extras +00:35 +793: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/route_arguments_test.dart: calendar route arguments router does not throw for malformed calendar extras +00:35 +794: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +795: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +796: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +797: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +798: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +799: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +800: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +801: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +802: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:35 +803: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +804: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +805: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +806: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +807: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +808: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +809: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +810: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:36 +811: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 390.0 +00:37 +812: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen_test.dart: keeps preparation edits mounted when spare time changes +00:37 +813: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:37 +814: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:37 +815: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:37 +816: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:37 +817: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:37 +818: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:37 +819: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:37 +820: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: Korean settings match the phone layout +00:37 +821: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:38 +822: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_export_test.dart: 암호화 백업 내보내기 password cancel disposes only after exit transition +00:38 +823: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:38 +824: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:38 +825: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:38 +826: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:38 +827: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/shared/router/notification_launch_widget_test.dart: production router gates, exact target, dedupe and retap from old B at 430.0 +00:38 +828: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +829: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +830: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +831: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +832: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +833: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:38 +834: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +835: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +836: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +837: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +838: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +839: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +840: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +841: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +842: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +843: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +844: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +845: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +846: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +847: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +848: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +849: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +850: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +851: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +852: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +853: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +854: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: validated preview cancellation never applies backup +00:39 +855: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_page_screen_test.dart: restricted notification state can open system settings +00:39 +856: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:40 +857: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: picker cancel skips preview and leaves data untouched +00:40 +858: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:40 +859: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:40 +860: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:40 +861: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: invalid selected file reports failure and permits another attempt +00:40 +862: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: committed restore reports partial then retry changes no durable data +00:40 +863: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/my_page/my_data_restore_test.dart: committed restore reports partial then retry changes no durable data +00:41 +864: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets official start prompt uses one action and opens alarm +00:41 +865: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +866: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +867: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +868: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +869: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +870: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +871: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:41 +872: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:42 +873: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:42 +874: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:42 +875: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:42 +876: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:42 +877: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:42 +878: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:42 +879: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:42 +880: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:42 +881: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:42 +882: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:42 +883: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI reset failure exposes retry without constructing the normal app +00:42 +884: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets manual finish before leave time sends lateness 0 and navigates +00:42 +885: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: pre-DI bootstrap failure can retry without showing private exception text +00:42 +886: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets manual finish after leave threshold sends positive lateness +00:42 +887: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets manual finish after leave threshold sends positive lateness +00:42 +888: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/startup/local_reset_recovery_test.dart: ko partial deletion screen is distinct from complete and fits large text +00:42 +889: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue shows live leave countdown for ongoing flow +00:42 +890: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue shows live leave countdown for ongoing flow +00:42 +891: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/preparation_flow_widget_test.dart: Preparation flow widgets completion dialog continue shows live leave countdown for early-start flow +00:42 +892: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:42 +893: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:42 +894: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:42 +895: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:42 +896: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:43 +897: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: shows English precise notification permission rationale +00:43 +898: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: granted Android notification permission enables notifications +00:43 +899: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: uses alarm language for iOS AlarmKit capability +00:43 +900: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: uses alarm language for iOS AlarmKit capability +00:43 +901: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: uses alarm language for iOS AlarmKit capability +00:43 +902: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: uses alarm language for iOS AlarmKit capability +00:43 +903: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/cubit/onboarding_cubit_test.dart: PreparationOrderCubit reorders immutable steps without losing durations +00:43 +904: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm_allow/alarm_allow_screen_test.dart: dismiss keeps notification delivery when fallback is available +00:43 +905: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/onboarding/screens/onboarding_screen_test.dart: start screen enters the entirely local onboarding flow +00:44 +906: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:45 +907: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:45 +908: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_screens_test.dart: weekly settings support multiple weekdays and positive count without yearly option +00:45 +909: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:45 +910: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:45 +911: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:45 +912: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:45 +913: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:45 +914: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_place_moving_time/schedule_place_moving_time_form_test.dart: edits place name and saves travel time picker selection +00:46 +915: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +916: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +917: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +918: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +919: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +920: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +921: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +922: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +923: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +924: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +925: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +926: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +927: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: four-step creation opens recurrence review and saves to the local database +00:46 +928: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: form review and cancel write nothing; confirmation saves owned preparation and alarms +00:47 +929: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: following edit loads the series base and preserves an individual name override +00:47 +930: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: invalid preparation keeps the draft and allows a successful retry +00:47 +931: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +932: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +933: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +934: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +935: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +936: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +937: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +938: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +939: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +940: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +941: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +942: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +943: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +944: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +945: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +946: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +947: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: ordinary save rollback and delivery-only retry ko +00:47 +948: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +949: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +950: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +951: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +952: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +953: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +954: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +955: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +956: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +957: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: renders preparation and user default spare time +00:48 +958: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: late receipt answer cannot submit or close replacement draft +00:48 +959: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: late receipt answer cannot submit or close replacement draft +00:48 +960: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/recurring/recurring_form_integration_test.dart: late receipt answer cannot submit or close replacement draft +00:48 +961: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:48 +962: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: saving spare-time picker submits chosen duration +00:48 +963: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_spare_and_preparing_time/screens/schedule_spare_and_preparing_time_form_test.dart: preparation edit route updates preparation from draft +00:48 +964: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: fills the bottom safe area while keyboard is hidden +00:49 +965: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: paints a white backplate behind the iOS keyboard inset +00:49 +966: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/keyboard_backed_bottom_sheet_test.dart: does not add keyboard backplate while keyboard is hidden +00:50 +967: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +968: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +969: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +970: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +971: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +972: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +973: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +974: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +975: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +976: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +977: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +978: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +979: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:50 +980: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +981: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +982: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: final submit does not close sheet immediately while submitting +00:51 +983: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/schedule_date_time/schedule_date_time_form_test.dart: shows explicit choices for a repeated DST time +00:51 +984: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: create screen initializes form bloc with selected date +00:51 +985: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: edit screen initializes form bloc with requested schedule +00:51 +986: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:51 +987: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:51 +988: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: sheet closes after successful submit +00:52 +989: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:52 +990: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:52 +991: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:52 +992: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:53 +993: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:53 +994: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_reorderable_list_test.dart: reorderable preparation list wires edits to row callbacks +00:53 +995: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_create_list_test.dart: create list forwards edits, reorder changes, and create request +00:53 +996: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:53 +997: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:53 +998: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:53 +999: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/components/schedule_multi_page_form_test.dart: cancelling date and time pickers keeps next disabled +00:53 +1000: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1001: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1002: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1003: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1004: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1005: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1006: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1007: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1008: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1009: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1010: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1011: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1012: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: editing a saved preparation step reports name changes and save +00:54 +1013: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: adding row delays interaction-ended while time picker is open +00:54 +1014: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation errors explain invalid name and preparation time +00:54 +1015: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: validation reports too-large preparation time +00:54 +1016: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: updates focus listener and field value when row identity changes +00:54 +1017: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/schedule_create/preparation_form/preparation_form_list_field_test.dart: row without remove callback renders as a plain editable tile +00:55 +1018: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-full-fix-focused.log b/plans/audit-2026-09-23/artifacts/a08/a08-full-fix-focused.log new file mode 100644 index 00000000..43731228 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-full-fix-focused.log @@ -0,0 +1,65 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: creates and reads a schedule with its place +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedules by date range and updates schedule fields +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: filters schedule date ranges as start-inclusive and end-exclusive +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/data_sources/schedule_local_data_source_test.dart: deleteSchedule removes only the requested schedule +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: missing schedule has typed absence while database failure stays an error +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: createSchedule should insert a schedule into the database +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: updateSchedule should update a schedule in the database +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: deleteSchedule should delete a schedule from the database +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getScheduleById should return a schedule from the database of given [id] +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate includes start date and excludes exact end date +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules between [startDate] and [endDate] +00:00 +11: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart: getSchedulesByDate should return a list of schedules later then [startDate] if [endDate] is null +00:00 +12: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +13: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +14: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +15: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +16: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +17: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +18: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +19: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +20: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +21: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +22: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +23: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +24: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +25: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +26: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +27: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +28: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +29: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +30: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +31: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +32: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +33: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +34: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +35: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +36: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +37: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +38: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +39: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +40: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +41: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +42: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +43: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +44: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +45: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +46: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +47: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +48: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +49: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +50: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +51: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +52: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:01 +53: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +54: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +55: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +56: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +57: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +58: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +59: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +60: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +61: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:01 +62: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_snapshot_test.dart: real encrypted backup cutoff cannot mix concurrent aggregate commit +00:02 +63: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-generated-policy.log b/plans/audit-2026-09-23/artifacts/a08/a08-generated-policy.log new file mode 100644 index 00000000..01eef09f --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-generated-policy.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Generated Dart policy is aligned: outputs are ignored and untracked. diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-handoff.md b/plans/audit-2026-09-23/artifacts/a08/a08-handoff.md new file mode 100644 index 00000000..9fba113f --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-handoff.md @@ -0,0 +1,36 @@ +# A08 implementation handoff + +Implementation base: `4529d9f84e911b1ee2fbb42af09698a0c27cc9f2`. Product/tests/fixture/ADR/dependency changes are unstaged; no commit, push, issue mutation, device mutation, or other worktree mutation was performed by this agent. Root owns publication and shared issue/state/handoff files. + +## Result and design + +- Production create/edit FormSubmission uses a single aggregate port. Schedule user fields, owned/copy-safe place, preparation mode/reference/owned steps, referenced former-owned definition cleanup, and exactly one durable revision commit together. A fault rolls all of them back. Custom step IDs are retained by position on edits; source template/default IDs are never adopted. Shared place renames create a new owned place instead of modifying another schedule's row. +- Fresh create drafts read default preparation and their baseline together; edit drafts read schedule, effective preparation, optional recurring segment and baseline in one transaction. Alarm planning materializes first and reads its immutable schedule/preparation snapshot in one transaction. Actual encrypted backup cutoff races produce a complete old aggregate, followed by a complete new live aggregate. +- Schema 3 adds nullable installation-local store/aggregate identities, versions and last mutation receipt metadata. Triggers assign identities before they are observable at the operation boundary and observe existing purpose-specific writers. An explicit stable rootSegmentId survives split and clock reversal. Definition/template/default/custom/place dependencies invalidate only dependent aggregate/root versions. Materializing new occurrences alone does not advance user-edit versions. +- New absent-target intents additionally fence the durable revision. An unrelated durable edit may conservatively reject a new create; the UI preserves inputs and requires explicit current-state review and a fresh intent. Existing edits use aggregate dependencies, not a global revision fence. The normalized intent digest also binds the captured baseline, preventing an old create from adopting a newly recreated row even if a caller reuses the mutation ID. +- Same current store/aggregate and durable identity+digest+version receipt can acknowledge a lost result without another commit. Plain matching contents cannot. Ordinary and following receipts survive actual file-DB close/reopen with a new memory gate; following retry works even after its original occurrence row was removed by the split. These tests are not native process-kill/device crash claims. +- Existing recurring create/occurrence/following transactions remain the write engine. Direct recurring create now raises typed conflict for an existing series. Only the aggregate receipt boundary may acknowledge a user retry. Unused RecurringSchedulesUseCase.save was removed after callsite search; read/review/delete APIs remain. +- Ordinary create explicitly constructs only user-owned fields; protected metadata injection is rejected. Existing edits issue partial user-field UPDATEs and recheck start/completion/frozen/preparation-start time. Legacy DAO create/update also strip concurrency metadata and read back post-trigger rows in the same transaction, preventing old persisted whole rows from rewinding version/receipt metadata. +- Schedule watch uses one SQL schedule/place snapshot with all dependency tables declared and preserves ascending scheduleTime. It does not create a second asyncMap read transaction. Actual subscribers do not observe rollback values, including an assertion after a committed drain barrier and before the next successful write. +- C01 integration returns durable receipt plus notification delivery status. DB success followed by reconcile failure is visibly committed; retry invokes only notification follow-up. Concurrent identical workflow saves coalesce. Generation is checked after asynchronous reconciliation. Opaque form ownership plus mutation ID and current route guard every asynchronous result/modal continuation. A partial receipt from form A cannot replace form B's real save. + +## Compatibility / integration obligations + +- Backup payload remains version 2; legacy versions 1 and 2 are accepted. Installation identities, root identity and receipts are excluded from portable payloads, including a strict recurring-segment key allowlist during decoding. Restore inserts obtain fresh store and aggregate identities. Existing profile upserts, partial settings/outcome updates and normal reopen preserve them. +- **A09 / D02 must issue a fresh store incarnation when replacing data, even if a future implementation preserves the profile row or user IDs/revisions.** Current backup revision is backup revision+1 and is not assumed monotonic across restore. Never substitute revision equality or memory generation=0 for store identity. +- Historical v2 fixture is an actual baseline Drift SQLite DDL dump. v1 is explicitly a reconstruction from that dump using the verified historical table diff, not a historical v1 binary execution. README includes full SHAs, method and hashes. Actual file SQLite migration checks v1/v2→3, legacy user/protected/order/recurring fields, identity preservation/reopen/upsert, atomic migration failure rollback and future-version refusal. Device SQLCipher migration remains D04's separate evidence boundary. +- New ADR 0037 records the real schema/conservative-create tradeoff. CONTEXT was not used as an implementation log. + +## Evidence + +- `/tmp/a08-validation.json`: final command results, hashes and coverage. +- `/tmp/a08-source-freeze.json`: final 49-path manifest. Earlier `/tmp/a08-source-freeze-v1.json`, `-v2.json`, and `-v3.json` remain available. v3 changes from v2 are only ScheduleDao and its ordering test. Final freeze changes from v3 only strengthen the existing materialization test; every product file is byte-identical to the successful full-suite version. +- `/tmp/a08-negative-proof.json`, `-negative.log`, `-negative-test.dart`: pre-change actual preparation INSERT failure left a committed schedule. Expected regression failed before implementation. +- `/tmp/a08-focused-final.log`: 112 focused tests before final DAO compatibility fix; `/tmp/a08-full-fix-focused.log`: 63 compatibility/boundary tests; `/tmp/a08-focused-v3.log`: 60 final-freeze changed-boundary tests. The successful full suite is 1018 tests at v3 (88.96%, 14113/15864 app-owned lines). Afterwards, the original count=3 materialization test was strengthened to count=65, proving actual creation of 5 more rows after the initial 60; root explicitly authorized focused validation without another full run. `/tmp/a08-materialization-probe.log` passes the independent probe; `/tmp/a08-materialization-final-focused.log` passes all 41 aggregate tests with the permanent strengthened assertion. Final analyzer is clean. +- `/tmp/a08-full-coverage-first-failure.log`: first full run 1008 pass / 7 fail; 5 failures exposed old whole-row metadata expectations, 2 existing A07 widgets exposed an extra async watch read transaction. The A07 test file remains unchanged and passes after replacing that watch with one SQL snapshot. `/tmp/a08-full-coverage-before-ordering.log` was interrupted for the later ordering review and is not passing evidence. +- `/tmp/a08-review-transcript.md`: actual additional questions Q1–Q4, root answers, correction of the restore monotonic assumption, and source/test reviews with actual responses. +- `/tmp/a08-ui/{ko,en}-{rollback,delivery-pending}.png`: four synthetic Flutter widget captures at 430×932, text scale 200%, Korean/English. Actual DB revision fault → draft preserved → save succeeds → notification-only retry leaves one row and one revision increment is tested in the same UI flow. All four images inspected; new error/partial dialog bodies and actions are reachable. Existing dimmed English form label wrapping/title truncation is broader U08 layout work; this does not certify the entire form's accessibility. + +## Evidence boundaries + +No mobile native alarm delivery, native file provider, device SQLCipher migration, external CI, merge, release or runtime process kill was performed. C01 schedule aggregate dependency is implemented; its A09 verified staging/session replacement and D02 damaged-store restore dependencies remain separate. Delivery tests use the real DB and workflow with controlled notification effects, not OS notification proof. diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-historical-dump.log b/plans/audit-2026-09-23/artifacts/a08/a08-historical-dump.log new file mode 100644 index 00000000..679a0f32 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-historical-dump.log @@ -0,0 +1,3 @@ +00:00 +0: loading /private/tmp/a08-schema-baseline/test/dump_test.dart +00:00 +0: freeze historical schema 2 DDL +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-l10n-freeze.log b/plans/audit-2026-09-23/artifacts/a08/a08-l10n-freeze.log new file mode 100644 index 00000000..53194980 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-l10n-freeze.log @@ -0,0 +1,2 @@ +Because l10n.yaml exists, the options defined there will be used instead. +To use the command line arguments, delete the l10n.yaml file in the Flutter project. diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-local-only.log b/plans/audit-2026-09-23/artifacts/a08/a08-local-only.log new file mode 100644 index 00000000..71a9ce0a --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-local-only.log @@ -0,0 +1 @@ +Running build hooks...Running build hooks...Local-only product boundary verified. diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-materialization-final-focused.log b/plans/audit-2026-09-23/artifacts/a08/a08-materialization-final-focused.log new file mode 100644 index 00000000..ac68ff1d --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-materialization-final-focused.log @@ -0,0 +1,43 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart +00:00 +0: place fault rolls back the whole aggregate and same intent retries +00:00 +1: schedule fault rolls back the whole aggregate and same intent retries +00:00 +2: step fault rolls back the whole aggregate and same intent retries +00:00 +3: revision fault rolls back the whole aggregate and same intent retries +00:00 +4: edit failure after old preparation deletion preserves old aggregate and revision +00:00 +5: concurrent and recreated repository lost receipt retry commit once +00:00 +6: same id other input conflicts instead of upsert +00:00 +7: semantic no-op preserves owned step IDs and revision +00:00 +8: two edit drafts and value ABA conflict without using global revision +00:00 +9: deleted create retry and recreated same id reject old intent +00:00 +10: new store same revision and fresh memory generation rejects old draft +00:00 +11: unrelated durable create conflict is explicit; reviewed new intent succeeds +00:00 +12: start between draft and commit conflicts and preserves protected fields +00:00 +13: same shared place label edit copies ownership +00:00 +14: invalid custom input is rejected atomically 0:00:00.000000 +00:00 +15: invalid custom input is rejected atomically 0:01:59.000000 +00:00 +16: create cannot inject outcome or frozen metadata +00:00 +17: recurring duplicate create has durable receipt and rejects changed input +00:00 +18: workflow failed delivery retry does not re-save and coalesces pending intent +00:00 +19: late delivery after replacement retains commit but never current success +00:00 +20: custom to default to template preserves reference sources and owned cleanup +00:00 +21: default fallback dependency ABA invalidates stale edit +00:00 +22: recurring preparation_definition_steps fault rolls back definitions series materialization and revision +00:00 +23: recurring recurring_schedule_segments fault rolls back definitions series materialization and revision +00:00 +24: recurring schedules fault rolls back definitions series materialization and revision +00:00 +25: recurring users fault rolls back definitions series materialization and revision +00:00 +26: following split keeps stable root through clock reversal and recovers lost receipt +00:00 +27: semantic same following save does not split or increase revision +00:00 +28: root receipt rejects dependency place mutation after following commit +00:00 +29: custom step 0 insertion failure has no partial aggregate +00:00 +30: custom step 1 insertion failure has no partial aggregate +00:00 +31: custom step 2 insertion failure has no partial aggregate +00:00 +32: clock reaching preparation start protects unchanged database baseline +00:00 +33: recurring RecurringEditScope.occurrence preparation_definition_steps failure preserves prior complete series +00:00 +34: recurring RecurringEditScope.occurrence users failure preserves prior complete series +00:00 +35: recurring RecurringEditScope.following preparation_definition_steps failure preserves prior complete series +00:00 +36: recurring RecurringEditScope.following users failure preserves prior complete series +00:00 +37: recreated create receipt cannot validate old baseline even with reused mutation identity +00:00 +38: occurrence owned definition invalidates series root receipt dependency +00:00 +39: legacy persisted row withPlace=false cannot rewind identity version or receipt +00:00 +40: legacy persisted row withPlace=true cannot rewind identity version or receipt +00:00 +41: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe.log b/plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe.log new file mode 100644 index 00000000..42a855da --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe.log @@ -0,0 +1,3 @@ +00:00 +0: loading /tmp/a08-materialization-probe_test.dart +00:00 +0: new occurrences materialized after receipt preserve its aggregate version and revision +00:00 +1: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe_test.dart.txt b/plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe_test.dart.txt new file mode 100644 index 00000000..b3f86a46 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-materialization-probe_test.dart.txt @@ -0,0 +1,32 @@ +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/core/database/local_data_operation_gate.dart'; +import 'package:on_time_front/data/repositories/recurring_schedule_repository_impl.dart'; +import 'package:on_time_front/data/repositories/schedule_aggregate_repository_impl.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; +import 'package:on_time_front/domain/recurrence/recurrence_rule.dart'; +import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; +import 'file:///Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_repository_test.dart' show schedule, prep; +void main() { + test('new occurrences materialized after receipt preserve its aggregate version and revision',()async{ + final db=AppDatabase.forTesting(NativeDatabase.memory()); + final recurring=RecurringScheduleRepositoryImpl(db,now:()=>DateTime.utc(2029)); + final repo=ScheduleAggregateRepositoryImpl(db,recurring,gate:LocalDataOperationGate(),now:()=>DateTime.utc(2029)); + try { + await db.userDao.putUser(const UserEntity(id:'local-profile',spareTime:Duration.zero,note:'')); + final intent=ScheduleFormSubmission(schedule:schedule(),preparation:prep(),preparationChanged:true,mutationId:'create',baseline:await repo.newBaseline(),recurrenceRule:RecurrenceRule(frequency:RecurrenceFrequency.daily,start:DateTime.utc(2030,1,1,10),timeZoneId:'UTC',count:65)); + await repo.save(intent,editing:false); + expect(await db.select(db.schedules).get(),hasLength(60)); + final root=await db.select(db.recurringScheduleSegments).getSingle(); + final revision=(await db.select(db.users).getSingle()).dataRevision; + await recurring.materialize(DateTime.utc(2030),DateTime.utc(2031)); + expect(await db.select(db.schedules).get(),hasLength(65)); + final after=await db.select(db.recurringScheduleSegments).getSingle(); + expect(after.aggregateVersion,root.aggregateVersion); + expect(after.lastMutationVersion,root.lastMutationVersion); + expect((await repo.save(intent,editing:false)).changed,isFalse); + expect((await db.select(db.users).getSingle()).dataRevision,revision); + } finally {await db.close();} + }); +} diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-negative-proof.json b/plans/audit-2026-09-23/artifacts/a08/a08-negative-proof.json new file mode 100644 index 00000000..53cfd8aa --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-negative-proof.json @@ -0,0 +1,13 @@ +{ + "baseline": "4529d9f84e911b1ee2fbb42af09698a0c27cc9f2", + "test_target": "test/data/repositories/schedule_aggregate_negative_test.dart", + "test_name": "preparation insert failure leaves no partial schedule, place or revision", + "exit_code": 1, + "observed": "Real SQLite preparation INSERT trigger aborts; the pre-change FormSubmission leaves its schedule row already committed. Test expected an empty schedules table and failed.", + "test_executed_before_product_edits": true, + "artifacts": { + "/tmp/a08-negative-test.dart": "692bce0eef10bde945c34f2741fa30f6ec7fa5964e605c638913ce8a85134e60", + "/tmp/a08-negative.log": "8f907e05b2250f2698569cd3006d0b126674549d71e86fc292eefb0408177f09" + }, + "boundary": "Actual NativeDatabase memory SQLite and real legacy repositories/use case, not mocks or a source-text assertion." +} diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-negative-test.dart.txt b/plans/audit-2026-09-23/artifacts/a08/a08-negative-test.dart.txt new file mode 100644 index 00000000..14fb397f --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-negative-test.dart.txt @@ -0,0 +1,41 @@ +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:on_time_front/core/database/database.dart'; +import 'package:on_time_front/data/repositories/schedule_repository_impl.dart'; +import 'package:on_time_front/data/repositories/preparation_repository_impl.dart'; +import 'package:on_time_front/data/data_sources/preparation_local_data_source.dart'; +import 'package:on_time_front/domain/repositories/timed_preparation_repository.dart'; +import 'package:on_time_front/domain/repositories/user_repository.dart'; +import 'package:on_time_front/domain/entities/user_entity.dart'; +import 'package:on_time_front/domain/entities/schedule_entity.dart'; +import 'package:on_time_front/domain/entities/place_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_entity.dart'; +import 'package:on_time_front/domain/entities/preparation_step_entity.dart'; +import 'package:on_time_front/domain/use-cases/create_schedule_with_place_use_case.dart'; +import 'package:on_time_front/domain/use-cases/create_custom_preparation_use_case.dart'; +import 'package:on_time_front/domain/use-cases/create_schedule_form_submission_use_case.dart'; +import 'package:on_time_front/domain/use-cases/schedule_form_submission.dart'; +import 'package:on_time_front/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart'; +import '../../helpers/noop_alarm_reconciliation.dart'; +class Runtime extends Fake implements TimedPreparationRepository {} +class Profile extends Fake implements UserRepository {} +class Effects extends Fake implements ScheduleMutationAlarmEffectsCoordinator { + @override Future call({required ScheduleMutationAlarmOperation operation, required String scheduleId}) async {} +} +void main() { + test('preparation insert failure leaves no partial schedule, place or revision', () async { + final db=AppDatabase.forTesting(NativeDatabase.memory()); + final schedules=ScheduleRepositoryImpl(database:db,timedPreparationRepository:Runtime()); + final preparations=PreparationRepositoryImpl(preparationLocalDataSource:PreparationLocalDataSourceImpl(appDatabase:db),userRepository:Profile(),database:db); + addTearDown(() async {await schedules.dispose();await preparations.dispose();await db.close();}); + await db.userDao.putUser(const UserEntity(id:'local-profile',spareTime:Duration.zero,note:'')); + final before=(await db.select(db.users).getSingle()).dataRevision; + await db.customStatement("CREATE TRIGGER reject_step BEFORE INSERT ON preparation_schedules BEGIN SELECT RAISE(ABORT, 'step failed'); END"); + final command=CreateScheduleFormSubmissionUseCase(CreateScheduleWithPlaceUseCase(schedules,Effects()),CreateCustomPreparationUseCase(preparations,NoopAlarmReconciliation())); + final draft=ScheduleFormSubmission(schedule:ScheduleEntity(id:'one',place:const PlaceEntity(id:'place',placeName:'Office'),scheduleName:'Meeting',scheduleTime:DateTime.utc(2030,1,1,10),moveTime:Duration.zero,isChanged:true,isStarted:false,scheduleSpareTime:Duration.zero,scheduleNote:''),preparation:PreparationEntity(preparationStepList:const [PreparationStepEntity(id:'step',preparationName:'Prepare',preparationTime:Duration(minutes:10))]),preparationChanged:true); + await expectLater(command(draft),throwsA(anything)); + expect(await db.select(db.schedules).get(),isEmpty,reason:'A failed aggregate must not leave its schedule committed'); + expect(await db.select(db.places).get(),isEmpty); + expect((await db.select(db.users).getSingle()).dataRevision,before); + }); +} diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-negative.log b/plans/audit-2026-09-23/artifacts/a08/a08-negative.log new file mode 100644 index 00000000..d1d22e3e --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-negative.log @@ -0,0 +1,17 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_negative_test.dart +00:00 +0: preparation insert failure leaves no partial schedule, place or revision +00:00 +0 -1: preparation insert failure leaves no partial schedule, place or revision [E] + Expected: empty + Actual: [ + Schedule:Schedule(id: one, placeId: place, scheduleName: Meeting, timeZoneId: UTC, occurrenceOffsetSeconds: null, scheduleTime: 2030-01-01 10:00:00.000, moveTime: 0:00:00.000000, isChanged: true, isStarted: false, scheduleSpareTime: 0:00:00.000000, scheduleNote: , latenessTime: 0, doneStatus: notEnded, startedAt: null, finishedAt: null, preparationMode: null, preparationTemplateId: null, preparationTemplateName: null, preparationTemplateDeleted: false, preparationFrozen: false, scoreContributionRecorded: false, recurringSegmentId: null, recurringSlotKey: null, recurringOrdinal: null, recurringOverrides: , preparationDefinitionId: null) + ] + A failed aggregate must not leave its schedule committed + + package:matcher expect + package:flutter_test/src/widget_tester.dart 473:18 expect + test/data/repositories/schedule_aggregate_negative_test.dart 37:2 main. + +00:00 +0 -1: Some tests failed. + +Failing tests: + /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/repositories/schedule_aggregate_negative_test.dart: preparation insert failure leaves no partial schedule, place or revision diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-ordering-focused.log b/plans/audit-2026-09-23/artifacts/a08/a08-ordering-focused.log new file mode 100644 index 00000000..b0fa803e --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-ordering-focused.log @@ -0,0 +1,13 @@ +00:00 +0: loading /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/data/daos/schedule_dao_test.dart +00:00 +0: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +1: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +2: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +3: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +4: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +5: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +6: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +7: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +8: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +9: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=false never navigates +00:00 +10: /Users/ejunpark/.codex/worktrees/1f98bd1e-95ca-4396-82c1-c34de41a8851/OnTime-front/test/presentation/alarm/screens/schedule_start_commit_widget_test.dart: real button awaits durable commit; failure retries and stale route=true never navigates +00:00 +11: All tests passed! diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-python-tests.log b/plans/audit-2026-09-23/artifacts/a08/a08-python-tests.log new file mode 100644 index 00000000..1a39f117 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-python-tests.log @@ -0,0 +1,16 @@ +test_boot_permission_must_not_expire_on_newer_android (test_android_notification_manifest.NotificationManifestTest.test_boot_permission_must_not_expire_on_newer_android) ... ok +test_disabled_application_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_disabled_application_is_rejected) ... ok +test_duplicate_receiver_declaration_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_duplicate_receiver_declaration_is_rejected) ... ok +test_exported_disabled_or_renamed_components_are_rejected (test_android_notification_manifest.NotificationManifestTest.test_exported_disabled_or_renamed_components_are_rejected) ... ok +test_invalid_xml_is_reported_as_failure (test_android_notification_manifest.NotificationManifestTest.test_invalid_xml_is_reported_as_failure) ... ok +test_missing_boot_actions_are_individually_rejected (test_android_notification_manifest.NotificationManifestTest.test_missing_boot_actions_are_individually_rejected) ... ok +test_missing_delivery_component_is_rejected_even_if_named_in_comment (test_android_notification_manifest.NotificationManifestTest.test_missing_delivery_component_is_rejected_even_if_named_in_comment) ... ok +test_missing_exported_attribute_is_rejected (test_android_notification_manifest.NotificationManifestTest.test_missing_exported_attribute_is_rejected) ... ok +test_receiver_must_be_direct_child_of_application (test_android_notification_manifest.NotificationManifestTest.test_receiver_must_be_direct_child_of_application) ... ok +test_source_manifest_can_deliver_and_restore_notifications (test_android_notification_manifest.NotificationManifestTest.test_source_manifest_can_deliver_and_restore_notifications) ... ok +test_valid_contract_without_explicit_enabled_is_accepted (test_android_notification_manifest.NotificationManifestTest.test_valid_contract_without_explicit_enabled_is_accepted) ... ok + +---------------------------------------------------------------------- +Ran 11 tests in 0.002s + +OK diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-review-transcript.md b/plans/audit-2026-09-23/artifacts/a08/a08-review-transcript.md new file mode 100644 index 00000000..743a18eb --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-review-transcript.md @@ -0,0 +1,97 @@ +# A08 implementation review transcript + +## Q1 — 최소 durable receipt 및 schema + +Agent: A08 구현 전 핵심 결정 Q1: 현재 schema 2에는 aggregate version/receipt가 없어 앱 재시작 후 같은 저장 의도와 ABA를 구별할 durable 근거가 없습니다. 추천은 전역 ledger 대신 Schedules와 RecurringScheduleSegments에 최소한의 incarnation/version 및 마지막 mutation ID+입력 digest를 두고 schema 3으로 migration하는 것입니다. 한 intent는 draft에서 stable UUID를 유지하고, 같은 receipt+입력일 때만 no-op이며 다른 편집/삭제·재생성 뒤에는 충돌합니다. backup v2는 사용자 콘텐츠 형식을 유지하고 이 설치 한정 receipt/version은 내보내지 않으며 restore 때 새 incarnation을 부여해 옛 intent가 통과하지 않게 하겠습니다. v1/v2 DB migration 및 backup v1/v2 compatibility를 A08 실제 테스트에 포함합니다. 이 schema 3 / backup 형식 유지 방향을 승인할까요? 구체 계획은 atomic aggregate repository → 일관된 edit/planning read → 기존 recurring tx를 감싸 receipt/protection → FormSubmission typed durable/effects 결과 및 실제 UI → actual Drift rollback/재시도/ABA/reader 검증 순서입니다. + +Root: Q1 방향 채택합니다. A08 합의에 schema 변경 필요 시 migration/backup 호환 검증 포함이 명시돼 있으므로 schema3로 최소 aggregate incarnation/version/last mutation ID+정규화 입력 digest를 추가하세요. 전역 ledger 없이 해당 aggregate 소유 테이블에 한정하며, schedule과 recurring segment가 각각 어떤 저장 의도의 권위인지/this-and-future split에서 receipt가 어디에 남는지 먼저 명확히 정리하세요. 같은 mutation identity+input+incarnation과 durable receipt가 실제 현재 aggregate에 대응할 때만 재시도 성공; 내용 같음만으로 과거 성공 추측 금지, 중간 편집·ABA·복원 후 old intent는 conflict. 원 생성 intent 뒤 부수적 materialization이 version을 어떻게 다루는지도 테스트해야 합니다. backup v2 형식 유지 및 설치 한정 receipt 제외 채택; restore에서 새 incarnation 부여하고 legacy v1/v2도 지원. v1→3/v2→3 actual SQLite migration 원자성·기존 사용자 필드/정렬/시작완료보호 보존, 신규 DB schema 대조, 미래버전 거부 회귀를 포함하세요. strict local-only 및 ignored generated 유지. 실제 추가 질문/답 전체 transcript 보존. 상세 반복 receipt 설계에서 판단이 필요하면 다음 질문으로 보내주세요. + +## Q2 — 반복 receipt 및 삭제 후 생성 재시도 + +Agent: A08 Q2 — 반복 receipt 권위는 series 최초 segment(분할 후에도 유지되는 root segment)로 두고, 이번 회차 저장은 해당 Schedule receipt + series root version을 함께 갱신, 이후 회차 저장은 기존 root segment receipt에 남겨 원 회차가 split 과정에서 삭제돼도 재시도를 확인하는 방향을 추천합니다. 새 회차 materialization 자체는 사용자 변경 version을 올리지 않되 기존 회차의 실제 편집/시작/완료/삭제 및 segment 변경은 version을 바꾸고 stale baseline을 거절합니다. 추가로 전역 tombstone 없이 '생성 성공→삭제→오래된 생성 intent 재전송'에서 행 자체가 사라지면 이전 생성과 최초 생성을 구별할 수 없습니다. 추천은 draft 생성 시 원래 target absence와 설치 generation 및 현재 durable revision을 생성용 baseline으로 잡고, ID가 없는 create의 첫 commit만 그 revision 확인을 요구하는 것입니다(편집은 aggregate version만 사용하여 무관 편집 충돌 없음). 이미 존재하는 동일 receipt 재시도는 current version으로 허용하고, 삭제 뒤 revision이 변한 old create는 거부합니다. 이는 새 생성 초안 동안 무관한 durable 변경이 생기면 재검토가 필요하다는 보수적 trade-off입니다. tombstone/전역 ledger를 추가하지 않고 이 생성 한정 revision fence를 채택할까요? + +Root: Q2 채택하되 경계 명확히 해주세요. 반복 create/이후 회차 변경 receipt 권위는 유지되는 최초 root segment, 이번 회차는 Schedule receipt와 root version을 같은 transaction으로 갱신. materialization만으로 사용자 변경 version을 올리지 않지만 기존 영향 회차의 편집/시작/완료/삭제 및 segment 변경은 stale 근거에 반영. split 후 원 occurrence가 없어도 성공 receipt를 root로 검증하고 root 삭제/재생성은 incarnation으로 구별. 생성 한정으로 absent target+durable revision+설치 generation baseline을 캡처해 최초 insert의 revision fence를 채택합니다. 무관 durable 변경으로 생성 충돌이 생기는 보수적 trade-off는 문서와 UI에 명확히: 초안 보존, 현재 상태 재확인 후 사용자가 새 저장 의도를 제출, 자동 baseline갱신/조용한 override 금지. edit에는 globalrevision fence를 적용하지 않습니다. 프로세스 재시작 때 in-memory generation이 0으로 돌아가도 old create/restore 후 intent가 통과하지 않도록 durable revision의 단조성 또는 설치 식별 근거를 실제 확인하세요. 기존 A04/restore monotonic 계약 재사용, 같은revision으로 복원해 우회될 수 있다면 추가로 질문. 실제 테스트에 create→delete→old retry, split후 lostreceipt/restart, unrelated edit→create conflict/draft preservation/new explicit intent를 포함하세요. + +## Restore revision assumption correction + +Root: root 소스 확인: 현재 BackupService._applyRestore L242는 dataRevision: Value(data.dataRevision + 1)로 백업 revision+1을 넣습니다. 따라서 현 restore가 설치의 이전 revision보다 단조 증가한다는 가정을 사용하면 안 됩니다. 생성용 revision fence만으로 process restart/restore 뒤 stale intent 안전성을 증명하기 부족합니다. 최소 durable installation/store incarnation을 유지/교체하는 설계 또는 restore revision 계약 조정이 필요할 수 있으니 실제 대안을 조사하고 질문해주세요. 단순 max(active,backup)+1로 바꾸더라도 reset/새 DB의 revision 재사용과 detached persisted draft의 관계까지 검토. A09 담당 전체 protocol을 지금 재작성하지 말고 A08 receipt가 새 store에서 재사용되지 않는 최소 경계를 확보하세요. + +Agent: Q3 — 지적하신 대로 restore는 backup revision+1로 재설정하므로 monotonic 가정은 철회합니다. 최소 대안으로 Users(Local Profile)에 설치 DB incarnation 1개를 schema3 additive column으로 추가하고 migration 때 기존 profile에 한 번 발급, 정상 재시작 때 유지, restore/reset의 새 profile INSERT 때 새 UUID를 발급하겠습니다. backup payload에는 포함하지 않습니다. 모든 draft/receipt는 이 store incarnation과 aggregate incarnation을 묶어 검증하여 restart에서 in-memory generation=0 및 같은 revision이어도 old intent를 거부합니다. 전역 ledger/새 replacement protocol 없이 기존 profile 생성·restore insert 경계만 사용하고, 미래 A09가 profile row를 보존하는 교체로 바꾸면 incarnation 재발급을 필수 계약으로 넘깁니다. 이 최소 durable store identity를 채택할까요? + +Root: Q3 채택합니다. Users의 additive store incarnation은 기존 profile에 migration 시 한 번 발급하고 정상 재시작/부분 설정·점수 UPDATE·기존 profile upsert에서 보존합니다. restore/reset의 새 profile 생성은 새 UUID, backup payload에는 제외. draft/receipt는 store+aggregate incarnation을 묶어 확인하므로 같은 revision/in-memory generation=0을 새 store 재사용 근거로 삼지 않습니다. profile entity mapper가 기존 upsert 시 default UUID를 덮는 A04 유사 회귀를 특히 검사하세요. migration 중 임시 빈/default 값이 runtime에 보이지 않고 기존/신규 row 모두 유효 identity인지 actualDB 검증. v1/v2backup import/export roundtrip에서 내부 identity가 유출/복원되지 않는지 확인. A09/D02의 profile-preserving 교체도 incarnation 재발급해야 한다는 통합 계약을 handoff에 남기세요. 이 결정은 영속 schema 변경과 보수적 create 충돌이라는 trade-off가 있어 skill ADR 양식으로 짧은 ADR 작성이 적절합니다. CONTEXT에는 구현 세부를 추가하지 마세요. 실제 monotonic 가정 철회/정정 문답 모두 보존해주세요. + +## Historical fixture review + +Root: 검증 주의: 현 test/core/database/recurring_migration_test.dart는 현재 schema를 만든 뒤 additive 열/테이블을 제거해 v1으로 낮추는 fixture입니다. schema3 migration 검증에서 새 모델로 old schema를 재구성한 테스트만으로 역사 v1/v2 호환을 주장하지 마세요. baseline4529d9f8의 실제 SQL/table 정의에서 frozen v2 fixture, v1 역사 정의를 확보하고 사용자 필드/started/finished/recurring 및 row identity 보존·실패 rollback·미래버전 거부를 실제 open 경로로 검증. D04 전체 SQLCipher/device 항목은 별도지만 이번schema변경의 호환성은 A08에서 완료해야 합니다. root는 지금 Flutter 실행하지 않습니다. + +Agent: A08 수정 전 actual Drift 회귀가 실제 실패했습니다(/tmp/a08-negative.log): 준비 INSERT fault 후 일정 row가 이미 남아 전체 rollback 조건을 위반했습니다. 현재 최소 schema3 identity/receipt와 aggregate repository를 구현 중입니다. Store identity는 profile insert trigger에서 발급해 기존 mapper/upsert가 UUID를 덮지 않게 했고, legacy writer까지 aggregate 변경을 관측하도록 제한된 SQL version trigger를 사용합니다. v1/v2 historical fixture는 baseline의 실제 DDL로 고정하여 migration 테스트하겠습니다. Flutter 독점 사용 중이며 아직 freeze/최종 검증 단계는 아닙니다. + +## Stable root, dependency version, normalized input and field ownership review + +Root: root WIP 리뷰(구현 중임 인지): 1) _root/SQLrootFor가 created_at,id 최소값으로 권위를 매번 고르면 시스템 시계 역행·동일timestamp/split 새 UUID 순서로 root가 바뀔 수 있습니다. root 소유권은 영속 안정 identity로 고정하거나 동등불변성 증명 필요. 2) template를 실제 preparationFor에서 실시간 읽는데 trigger는 preparation_template_steps/템플릿 삭제·수정이 연결 aggregate version을 갱신하지 않습니다. 편집초안 이후 참조 template 변경/삭제 또는 definition 변화, nullable/legacy emptycustom defaultfallback도 stale 근거에 반영해야 합니다. 모든상태를 무조건 globalrevision로 막지 말고 의존중인 aggregate만. 3) _steps digest .inMinutes는 UI 분단위정책이라면 저장validator도 정수분을 강제해야 합니다. 60초/119초가 같은 digest로 다른 intent내용을 동일 성공 처리하는 경계 방지(legacy 읽기는 보존). 4) 신규 create가 input.copyWith를 whole mapper로 넣으므로 malicious/default submission의 isStarted/finished/score/frozen/recurring metadata를 create에 유입시키지 않는 명시 필드 정책 확인. 각 실제 검토와 답 transcript에 보존 부탁합니다. + +Agent: WIP 리뷰 4개 수용합니다. root는 신규 rootSegmentId 설치 한정 필드로 고정하며 최초 insert 때 series 기존 root를 상속, migration 때만 한 번 기존 first root를 골라 모든 segment에 backfill합니다. 이후 시간 역행/split UUID 순서로 재선택하지 않습니다. template/definition/default fallback 의존만 version trigger로 추적하고 실제 source mutation/ABA 검증을 추가합니다. 사용자 step 입력은 정확한 정수분(1–1440)만 허용하고 기존/backup decoder는 변경하지 않습니다. 신규 create에서 started/finished/score/frozen/recurring ownership 주입은 typed invalid로 거절합니다. 각 지적/답을 transcript에 보존 중입니다. + +## Delivery and form currentness review + +Root: 추가 WIP currentness 리뷰: ScheduleSaveWorkflow.retryDelivery는 effects.afterCommit await 뒤 repository.isCurrent(receipt)를 재검사하지 않으면 old-generation reconcile 완료를 현재 성공으로 표시할 수 있습니다. C01에서 다룬 것처럼 최종 generation 검증 및 route/current form owner 확인을 연결하고 deterministic pending reconcile→restore/reset→late completion 회귀 포함해주세요. 또한 같은 intent 동시 save가 DB중복은 안 만들어도 delivery 요청2회/UI이중navigation이 될 수 있으니 workflow/UI의 pending identity와 완료 receipt 처리 정책을 명확히 하세요. 통합 완료 시 재시도는 DB를 다시 호출하지 않는 actual widget/workflow 증거 필요합니다. + +Root: 최종 UI 검증에 포함 부탁합니다: 동일 Bloc에서 A 일정 save/review Future가 pending인 동안 B edit/create draft가 로드되거나 route가 바뀐 뒤 A 완료가 B state/success/navigation을 변경하지 않아야 합니다. 현재 WIP _submit은 isClosed/data generation만 확인하고 state 전체를 최신 state.copyWith로 바꾸므로 같은 세대의 다른 form owner도 구분해야 합니다. conflict reviewCurrent→dialog acceptance 또한 당시 form id/mutation identity와 baseline을 묶어야 합니다. 같은 pending save 중 입력/다른 event가 submissionStatus를 idle로 바꿔 중복 처리하는 경계도 actualBloc/widget 회귀로 확인하세요. 제품코드 반복수정 요청이 아니라 합의된 old-callback AC에 필요한 구체 시나리오입니다. + +## Committed watch, form replacement and localization review + +Root: SQL trigger 도입 시 Drift invalidation도 최종 검증해주세요. template/default/preparation writer가 trigger로 schedules/root version을 갱신해도 Drift는 원래 쓰기 테이블만 알 수 있습니다. 실제 watch가 의존 테이블을 포함하거나 commit 후 정확한 table invalidation을 받아야 하며 transaction rollback 때 성공 stream을 수동 발행하면 안 됩니다. 실제 aggregate/planning stream subscriber가 template/default/custom 변경 후 committed 최종값만 관찰하는 회귀가 필요합니다. 또한 recreate repository만은 앱/PID재시작 증거가 아니므로 durable receipt 검증은 최소 fileDB close/reopen/newgate로 분리 표기하세요. + +Root: 최종 diff에서 ARB 전체 formatting churn도 정리해주세요. 현재 app_en.arb 1227줄/app_ko.arb424줄 변경은 새 A08 문자열 양보다 크게 보입니다. 기존 배열/들여쓰기 형식 보존하고 필요한 key만 추가해 실제 변경이 리뷰 가능하도록 해주세요. tracked l10n 산출물은 새 ARB와 일치하게 재생성, sourcefreeze는 이 정리 뒤로 잡으면 됩니다. + +Root: snapshot test 리뷰: 'real preparation watch ... never rolled back value'는 rollback 직후 values/cache에 99가 없는지 확인 없이 다음 성공 save로 이어져, 실패 때 99가 잘못 발행되고 성공 때 중복발행이 없으면 values99==1 조건을 통과할 수 있습니다. 실패가 반환된 뒤 authoritative stream/cache가 여전히10임을 독립 assertion/정상 drain barrier로 확인하고 다음 commit이 실제 끝난 후99 발행을 구별해주세요. 고정sleep 대신 watch/DB barrier 사용. 동일 root버전/template 의존 stream은 최종 신규회귀에 포함 예정인지도 확인 부탁합니다. + +Root: 구체 신규폼 회귀 발견: ScheduleFormState.copyWith의 saveReceipt는 `saveReceipt ?? this.saveReceipt`이고 _emitLoadedDraft가 초기화하지 않아 A의 deliveryPending receipt가 B 새 draft에 남습니다. _submit은 그 receipt가 있으면 B 저장 대신 A 알림만 retry하므로 B가 저장되지 않았는데 success/navigation될 수 있습니다. nullable receipt/baseline/originalSchedule 등 폼교체용 초기화 경계를 명시하고, A partial→B create/edit→submit에서 B 실제 저장·old receipt 미재사용을 actual Bloc/workflow로 검증해주세요. 기존 originalSchedule도 copyWith(null)로지워지지 않는 경계이므로 새 draft는 fresh state 구성하는 방안이 더 명확할 수 있습니다. props에 saveFailure 중복도 보입니다. + +Agent: 해당 폼 교체 문제는 바로 전 수정에서 _emitLoadedDraft를 fresh ScheduleFormState 생성으로 변경했습니다. 이전 receipt/originalSchedule/recurrenceRule/validation을 새 초안에 유지하지 않습니다. A partial→B submit 실제 회귀를 추가하겠습니다. 현재 신규 aggregate+snapshot+backup+Bloc 확장 52건은 통과했고, 별도 역사 migration 5건도 통과했습니다. stream rollback 검증은 실패 후 별도 정상 dependency commit을 drain barrier로 삼아 99 발행이 없음을 다음 성공 전에 확인하도록 보강했습니다. root/template watch version 회귀도 추가 중입니다. + +## Q4: internal recurring duplicate policy + +Agent: 추가 구현 경계 질문 Q4: 제품 FormSubmission은 새 aggregate port만 사용하며 durable identity/receipt로 일반·반복 create/edit를 검증합니다. 기존 RecurringSchedulesUseCase.save는 이제 제품 callsite가 없지만, 내부 RecurringScheduleRepository.create는 기존 단위 테스트와 aggregate adapter가 직접 호출하며 `series exists => return`을 아직 갖고 있습니다. 추천은 이 내부 함수의 existing-series 조용한 성공을 typed conflict로 바꾸고, 중복 사용자 저장의 성공 재시도는 반드시 aggregate receipt 경계로만 처리하게 하는 것입니다. 기존 direct repository 반복-create 테스트는 그 명시적 내부 충돌과 aggregate 재시도 1commit을 각각 검증하도록 바꾸고, 미사용 RecurringSchedulesUseCase.save는 제거하여 우회 저장 entrypoint를 남기지 않겠습니다. 이 경계 정리를 채택할까요? + +Root: Q4 답변: 채택합니다. 내부 recurring create에서 series exists를 조용한 성공으로 처리하면 다른 입력/오래된 intent가 성공으로 오인되므로 typed conflict로 바꾸고, 사용자 재시도 성공은 aggregate의 동일 store/aggregate incarnation + intent/digest + durable receipt 검증으로만 허용하세요. 실제 전체 callsite 검색으로 미사용임을 확인한 RecurringSchedulesUseCase.save만 제거하고, 읽기/기타 사용 API는 보존하세요. 기존 direct repository 테스트는 명시적 충돌, aggregate 테스트는 동일 intent 1commit/no-op 및 다른 intent/input 충돌을 검증하세요. 이 Q4와 답변도 실제 원문 transcript에 포함하세요. 새 작성 화면 stale receipt 회귀와 앞서 요청한 watch rollback 중간 관측 검증도 최종 결과에 포함해 주세요. + +## Final dependency / draft / modal ownership review + +Root: 추가 root source검토: schedule_aggregate_schema의 place_version/template_steps/template/preparation_default는 schedule.aggregateVersion만 증가시키고 recurring root version은 갱신하지 않습니다. rootReceipt 재시도는 row version보다 먼저 root receipt로 성공하므로, 실제 recurring에 의존 가능한 writer라면 root에 마지막 receipt를 남긴 뒤 해당 발생의 place/template/default 변경→같은 following intent 재시도가 stale 성공일 수 있습니다. recurring definitions가 항상고정되어 template/default는 불가능한지 실제writer로 확인하고, 가능한 의존변경(place 등)은 root도 invalidate하거나 root receipt 검증에 의존version을 포함하세요. 단순 모든global변경 invalidate말고 실제의존대상만. 현재 rootReceipt의 lastMutationVersion이 동일하고 row만바뀐 경계 테스트 부탁드립니다. 불가능하면 실제소스근거로 transcript에 답 남기면 됩니다. + +Root: 추가검토: LoadScheduleFormDraftUseCase.create가 defaultPreparation을 먼저읽고 await 후 newBaseline을 읽습니다. 두읽기사이기본준비가변경되면 옛준비표시+새revision baseline이어서 createfence가이를잡지못하고 저장시새default사용/사용자가custom으로바꾼옛default복사 가능. A08 snapshot 계약이 edit뿐아니라새draft에도해당하는지 확인하여 production create의default와baseline을한snapshot으로읽도록port를묶거나 baseline먼저읽고뒤에preparation읽어중간변경시반드시conflict되게하세요(전자가명확). latch 회귀로default/baseline혼합없음을검증. optionaltestfallback을production계약검증대신쓰지않아야합니다. + +Root: UI await후ownership 마지막검토: schedule_multi_page_form의recurrence review/timeChoice는현재bloc.isClosed만, genericfailure retry는mounted만확인합니다. 같은Bloc에서A질문sheet열림→Bdraft전환→A응답이돌아오면B에confirmed/excludedSlots/timechoice/submit이적용될수있습니다. deliveryPending dialog도ModalRoute.isCurrent만으로same-route Bowner를구분못합니다. root앞선 formowner요청을UImodal까지일관되게적용해 originalstate의owner/token을capture하고모든await후동일owner일때만후속이벤트/routepop/snackbar를허용하세요. source변경없이형태만다른새draft를state.id만으로검증하지않도록. 제품widget회귀로최소늦은review/receipt응답이새draft를변경/닫지않는것을확인해주세요. + +Agent: 최신 검토 3건 모두 실제 경계로 확인했습니다. create default+baseline은 새 aggregate.newDraft transaction으로 묶었고, UI modal은 공개 opaque formOwner와 mutationId를 함께 캡처하여 모든 await 후 동일 owner+현재 route를 검증하도록 보강했습니다. place/template/default 의존 trigger의 root version 갱신을 추가 중입니다. 기존 신규 집중 52건+역사migration5건은 통과했으며, 이후 추가한 실제 UI ko/en 재시도 QA는 첫 화면의 비동기 validation→render 타이밍을 테스트 harness에서 조정 중입니다. 남은 것은 신규3경계 회귀, 실제 UI 캡처, focused 전체/analyze 정리 후 freeze 및 fullcoverage/정책검사입니다. Q4 채택 구현 완료, 원문 transcript도 최종 병합하겠습니다. + +Root: 역사fixture 마감시 test/fixtures/database에짧은provenanceREADME또는최종validation에서 schema_v2=baseline4529실행dump, schema_v1=ccc6272대조/추출방법·실제sourcecommitfullSHA·명령·fixturehash 명시해주세요. v1을실행생성한것인지 v2DDL에서과거diff에따라재구성한것인지정확히구분해야합니다. 역사실행binary/SQLCipher실증으로과장하지않고D04가실제암호화migration까지연결할수있게하면됩니다. + +Agent: 확장 경계 검증 45건 통과했습니다(/tmp/a08-final-boundaries2.log): 일반 첫/중간/마지막 단계 rollback, 반복 이번/이후 회차 준비·revision fault와 재시도, 이미 split된 마지막 segment 동일값 no-op, fileDB close/reopen+new gate 후 원 occurrence가 사라진 following receipt 복구, 실제 암호화 backup cutoff 경쟁까지 포함합니다. UI+Bloc 26건도 통과, ko/en 200% 텍스트 저장실패/DB재저장 없는 delivery retry 캡처 생성했고 2장 시각 확인했습니다. 신규 draft snapshot/root place 의존성 회귀 통과. 보완 중 발견한 occurrence-owned definition→root version 의존과 create baseline digest binding까지 추가하여 최종 analyzer 정리 중입니다. fixture README에는 v2 실제실행dump와 v1 역사diff 재구성을 구분하고 fullSHA/hash를 기록했습니다. 새 sqlite3 다운로드/upgrade 없이 기존잠금2.9.4를 dev dependency로 명시했습니다. + +Root: root가 /tmp/a08-ui의 KO/EN rollback/delivery-pending 4PNG 모두 직접 열어 확인했습니다. 새오류/partial본문·행동은200%에서접근가능합니다. EN dimmedform의Preparation 단계라벨줄바꿈/준비필드제목말줄임은기존U08전체레이아웃범위로구분하되A08전체form접근성이완료됐다고표현하지마세요. native실기기아닌syntheticwidgetcapture, actualDBfault/retry동작은같은테스트별도evidence로연결. finalsource변경후해당캡처영향없음/freezehash확인부탁드립니다. rootlegacy14issues read-only일치확인후누락hash만관리파일에추가중이며제품수정없음. + +Agent: A08 제품·테스트·fixture·ADR·pubspec 46개 경로를 /tmp/a08-source-freeze.json으로 고정했습니다(정확한 count는 manifest.paths로 확인 가능, sourcefreeze 생성 출력 대기). implementation base는4529d9f8, freeze 시HEAD는문서커밋반영fb874a4c입니다. ignored generator/l10n 재생성 후 final focused suite 및4PNG 재캡처 실행 중이며 다음순서 analyze→fullcoverage1회→필수gates입니다. 제품추가설계 결정 없음. scope cleanup은 변경 전 해당 schedule/series가 실제참조한 owned definition만 후보로 잡고 commit후참조없을때 삭제하여 무관orphan청소/semanticno-op부작용을 피했습니다. + +## Full suite regression review and corrections + +Root: root확인: 첫full /tmp/a08-full-coverage.log=1008pass/7fail, DAO/localsource5개는새metadata전체rowequality, startcommitwidget2개는L133 5초latchtimeout입니다. 원본실패로그별도보존후수정/refreeze필요. DAO비교를단순삭제하지말고사용자domain필드전체보존+identity발급/version증가를분리검증하세요. 주의ScheduleDao.insertReturning/writeReturning은SQL AFTER trigger값이반영되기전row를돌려줄수있어create반환null/update반환oldversion이실제DB값과다릅니다. product소비자가메타데이터를쓰는지확인하고필요하면sameTX내readback으로반환계약정리; legacy toCompanion(true)로실제persisted identity/version을재설정할위험도함께조사. startwidget은왜latch도달안하는지실제stream/read변경근거로고치고timeout늘리기만하지마세요. freeze47 manifest e7e9...는현단계구버전으로보존하고새manifest재동결시변경범위명시. + +Root: legacymetadata위험구체화: DB에서읽은Schedule에는non-null aggregateVersion/lastMutation*가있습니다. 그row를보관한뒤다른writer가version을올리고,옛row.copyWith(...).toCompanion(true)를updateSchedule/updateScheduleWithPlace에주면nonnull옛version/receipt를함께write해서AFTERtrigger의+1이현재/과거receiptversion과다시같아질수있습니다. SQL원문trigger외에도기존DAO수정경로에서concurrencymetadata는write권한없음으로빼거나최소동등한보호가필요합니다. 실제두번편집/oldrow재사용/ABA 뒤oldaggregateintent가성공no-op으로오인되지않는회귀로확인하세요. + +Agent: 동일 원인 확인했습니다. 기존 DAO의 whole-row legacy update에서 metadata는 명시적으로 Value.absent로 제외하고, create/update 반환은 같은 transaction 내 최종 readback으로 바꾸겠습니다. row equality 5건은 전체 domain 값 + 발급 identity/version의 독립 검증으로 전환하고 old persisted row 재사용/ABA 회귀를 추가합니다. start widget latch는 새 watch의 asyncMap→별도 DB transaction이 Flutter fake-async zone에서 열린 read transaction을 남긴 채 runAsync write를 대기시켜 생긴 것으로 확인 중입니다. schedule+place watch를 dependency 선언을 유지한 단일 SQL snapshot으로 바꾸면 추가 비동기 transaction 없이 기존 시작 경로를 유지할 수 있어 이를 집중 검증하겠습니다. 첫full실패/47path구freeze는별도보존 후수정3제품/테스트범위 refreeze합니다. count정정: 기존manifest정확히47개입니다. + +Root: startwidget timeout추가가설(검증필요): ScheduleRepository 생성직후 scheduleDao.watchScheduleList가 이제 asyncMap→db.transaction(getScheduleList)라, widgetfakeAsync zone에서pending watch transaction이생기면 tester.runAsync로await하는start transaction앞에queue되어 fake-zone continuation을pump하지않아막힐수있습니다. 첫tap/ObservedSession.entered 여부와실제queue를확인해초기watchsettled/readbarrier를harness에서실제펌프하거나제품streamqueue교착이면해결하세요. 단순5초증가/skip금지. 원래A07test의DBfault실패/재시도/route증거는유지해야합니다. + +Root: 최신DAO독립리뷰: watchScheduleList의변경전쿼리는 scheduleTime ASC orderBy가있었습니다. 현재단일SQL snapshot에는 ORDER BY가없어기존구독순서계약이유실됩니다(앞선asyncMap(getScheduleList)도같은누락). `ORDER BY s.schedule_time ASC`를보존하고같은시각tie는기존미정계약을새로추정할필요없습니다. 실제역순삽입→watch결과시간순 회귀가필요합니다. create/update sameTXreadback 및_withoutConcurrencyMetadata는방향맞고후보63테스트pass로그확인했습니다. 이정렬보완까지재freeze해주세요. + +Agent: 정렬 누락 수용했습니다. watch 단일 SQL에 ORDER BY s.schedule_time ASC와 역순 삽입 실제 watch 회귀를 추가했습니다. A07 테스트 파일은 전혀 수정하지 않았으며 원래 fault→retry→route 두 테스트가63건 집중 실행에서 통과해 asyncMap read-transaction 큐 원인을 뒷받침합니다. second full은 정렬 검토 도착 뒤 정확히 소유 PID1537에 SIGINT로 중단했고 로그를 /tmp/a08-full-coverage-before-ordering.log로 보존(통과 근거로 사용 안 함), v2freeze도보존했습니다. v3 최종freeze는49경로이며 v2대비DAO+DAO정렬test2경로만바뀝니다. 전체domain필드·발급identity·posttrigger version readback 비교와 legacyoldrow두경로 ABA 회귀 유지했습니다. + +## Materialization proof scope correction + +Root: root최종현재확인: full1018pass(55s),coverage88.96%(14113/15864),v3freeze49경로모두현재파일해시일치(40c22c1505b5ea38b1434904d26bd1c36a531996ac917234cc428f5528002beb),gitdiffcheck빈결과. handoff/negativeproof읽었습니다. validation JSON 완성후제품/Flutter소유권해제명시하고전달해주세요. 이후root가sourcecommit/blobhash검증·증거pack/#601전체문답·C01의A08진행기록·pushCI를합니다. 추가검증확장요청없습니다. + +Agent: 최종 JSON 작성 중입니다. 마지막 증거 매핑에서 기존 materialization 회귀의 count=3은 create가 이미 세 회차를 모두 materialize하여 실제 새 회차 추가가 아니라 재호출 no-op만 증명함을 발견했습니다. 제품/49path freeze는 그대로 유지하고, 별도 /tmp actualDB probe로 count65 최초60→materialize65 후 root version/revision/동일receipt 불변을 한 번 확인해 원래Q1 요구의 빈틈만 닫은 뒤 즉시 소유권 해제하겠습니다. 제품/회귀소스 추가확장이나 full재실행은 하지 않습니다. + +Root: 실제새회차materialization빈틈확인은필요합니다. 우선별도actualprobe로확인하되,그핵심assert(create60→추가5/rootversion·revision·sameintentreceipt불변)는기존count3 no-op회귀를대체하는영구테스트로남기는편이좋습니다. 제품소스변경없으면해당test변경만재동결+집중재실행하고, full1018은직전동일제품소스/이test보강전결과라고정확히기록하세요(전체재실행불필요;곧최종commit원격CI가전부실행). 원probe/추가회귀범위·최종manifestdelta를분명히하면됩니다. 이Q1필수검증빈틈외에새scope확장없음. diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v1.json b/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v1.json new file mode 100644 index 00000000..798f019b --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v1.json @@ -0,0 +1,54 @@ +{ + "frozen_at": "2026-09-24T03:44:58.166324+00:00", + "implementation_base": "4529d9f84e911b1ee2fbb42af09698a0c27cc9f2", + "head_at_freeze": "fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f", + "paths": { + "docs/adr/0037-bind-schedule-save-receipts-to-local-aggregate-identities.md": "8bfef8e2fd05f0ad9a7e495475d534d5b6d5a4b0d5be33df77dcf065f3ced964", + "lib/core/backup/recurring_backup_data.dart": "875221d414768e380ec7d6f731f371840c1f39ca8efe8dc88b75949c22caf5b7", + "lib/core/database/database.dart": "c217c5ffcd37825f480c3ff303316ba2db8680d1221138fccffda7d6016f1cef", + "lib/core/database/schedule_aggregate_schema.dart": "6fcc4e2dce5a78d0cb435777782573aac03eaf1152451875267cc9c4d7f9a312", + "lib/data/daos/schedule_dao.dart": "9fce83577b8063066e74b0f3b474817e4c258d10f8a8976891c515c2be9df084", + "lib/data/repositories/alarm_repository_impl.dart": "bb32b840e90f68e9073f05022ff1ebac6cace18abb078afc02af62e7990de45e", + "lib/data/repositories/preparation_repository_impl.dart": "bb430d86ec37a7b3df629c44f0fa34dbd51b48102dd723d9d9d78446b56fbc80", + "lib/data/repositories/recurring_schedule_repository_impl.dart": "dbb70fd633b08d5a81619d33b1feb7938bcfffde1825bc0a53fdbe1118bf92fb", + "lib/data/repositories/schedule_aggregate_repository_impl.dart": "11df5dfdc0fb4f0c292db82edc12e789ee0bd9070cd2956f6e8c6ed2383e2e9a", + "lib/data/tables/recurring_schedule_tables.dart": "d1e76b7157a0ffbfef018ccebb28ffd1848d7cf56cebfa94a69651ea6574e3bb", + "lib/data/tables/schedules_table.dart": "33b39eb13aa513e01723da00f4914da0d6cf8d1ed07e16a0070bf9f6a800aa18", + "lib/data/tables/user_table.dart": "b21c5c98f36f7237b9d632d8afe41fff4d665235f36cc8a2e146178c9b67b046", + "lib/domain/entities/schedule_save.dart": "9fd020ed04d81f3fc8f552cf9b9437f27bd6f74159cb038bbc1590909dde3b76", + "lib/domain/repositories/schedule_aggregate_repository.dart": "3755c4135d26ee85bbfae3a19c56395a2e444f6cf6c89cb335d23b3af3df5e61", + "lib/domain/use-cases/create_schedule_form_submission_use_case.dart": "839b370b1ee876612a761848d2fb038c6f83741d520de05c9ca9e4c0ddec72c8", + "lib/domain/use-cases/load_schedule_form_draft_use_case.dart": "0cc6587d76d5fb0194b7bf47cb21169e26bb043fc9a1fd8bd72da8fbbc37ff34", + "lib/domain/use-cases/recurring_schedules_use_case.dart": "9384895177fe325a067f42388b59a704dca3966aa932de84346fb94d90ff317f", + "lib/domain/use-cases/schedule_form_submission.dart": "3b05cab96ec691388eac5007f1791be25c88ef3cacfd34a2bb19c6fa69514d79", + "lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart": "c57bd8d8fc3204e920f677925d2ce5f1f6953062d38bef41c2cb1c51f465b6b5", + "lib/domain/use-cases/schedule_save_workflow.dart": "3eb8dcbbe095a31a972c95a56360b8275082b5a07a0e946d6d05c931ff971477", + "lib/domain/use-cases/update_schedule_form_submission_use_case.dart": "975215ea2d3e869b3da5c9e5c3acb643a79f869f18f81fa37289ac4ae5a8a4fa", + "lib/l10n/app_en.arb": "213a419f55776010ab7f980b83b19b56e13580d6a7c7c229eb692aea7e5f93bf", + "lib/l10n/app_ko.arb": "48b5c1fd482ac93d403b21c5e218e6645cb8cb55f36ddf8c99ca90f850252db0", + "lib/l10n/app_localizations.dart": "7298fb334a699579aaac77fc05dd354f32ba9bd714968f8cf03fe403a0318ec6", + "lib/l10n/app_localizations_en.dart": "14c3741c7e7307f61673d994ad5e44cd742a5f5e26b80fd9783225286ecee827", + "lib/l10n/app_localizations_ko.dart": "660c8d17c39a7e305519876053aa4dbf6a945f8ad3b03ef4ce13abddf7847c25", + "lib/presentation/schedule_create/bloc/schedule_form_bloc.dart": "6642f6ad29d36434ecb3dbb879d09b6f2a30759c529f0d9ea5cdb68e28e5a69a", + "lib/presentation/schedule_create/bloc/schedule_form_event.dart": "9f7a765946c18aea23d8fb97917ce100d6f40bc2d52bd63027a3d472cd086b56", + "lib/presentation/schedule_create/bloc/schedule_form_state.dart": "b692aa1667462f547662c2c57beab1b2b8b70dd04a893e432761ce96f0b64f7c", + "lib/presentation/schedule_create/components/schedule_multi_page_form.dart": "10a04857519473eb7c83fa6beb5958d3c62fa6b3139147105d8d49a860b8453a", + "pubspec.lock": "1305211851a74cdea3bae6402409b9ac0108562751859b062bb90aeb34900d4f", + "pubspec.yaml": "71d14bb6731407309b150f89e548ec4d78fa0843087e1b2a8d1489425697eaf1", + "test/core/backup/backup_service_test.dart": "92d76777b8cc5a4205fa33075281997313cf50f99b38271edd4e16718d6903f2", + "test/core/database/database_index_test.dart": "ead0ee32b368a911832fb1e08e86ec684eb767949d7ee060022ad6c81ceec898", + "test/core/database/recurring_migration_test.dart": "03a0745c00ea247c55dea9f65b27a435b0492b217520c0f75fab4a898b672c0d", + "test/core/database/schedule_aggregate_migration_test.dart": "46bf6aa216d7e46c47730e8aa0e87216984b6551dd1ec015cfa3f7d84e619d91", + "test/data/repositories/recurring_schedule_repository_test.dart": "60969859e40aaf9b5301d5c1340c6ff201bb95466b50ce1369a5677f751cec05", + "test/data/repositories/schedule_aggregate_repository_test.dart": "bc1d814a5ffb2949724a519da691aa62c44a00fd0055ade234c5e335f568b15d", + "test/data/repositories/schedule_aggregate_snapshot_test.dart": "50531d54e9eed6b93414185c62326f5cd6489c46eaf543bb08848511518efbca", + "test/domain/use-cases/create_schedule_form_submission_use_case_test.dart": "317a45914cb0490bb54fe25cfbdbb213ff0a95697820834774dd2936f88d6e67", + "test/domain/use-cases/update_schedule_form_submission_use_case_test.dart": "5fd739f2cbaada8a787d14d1af8dd7b9718df86027d356919ed7669b5f78b6a9", + "test/fixtures/database/README.md": "1e4068750433db1b18c5936216786128def1088f532d5a9486d9e9405625f168", + "test/fixtures/database/schema_v1.sql": "782cd3a3cfdca56c67f896a7fbdfa9791a349f77b2b65e21c7f0c0f2ce4a121d", + "test/fixtures/database/schema_v2.sql": "a7ec3c57f1c86a58ef45c3417abe187fa4e44a0be87e3861a7182cb3ab02d7c5", + "test/presentation/recurring/recurring_form_integration_test.dart": "6d935c38090713b0a729f8fe70a7ff4ff018d4de0f8fa508ab201040988422c9", + "test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart": "15bffe233e663f55d6588533dd272ccdd8658ef11be709e3708c8241cf18c954", + "test/presentation/schedule_create/components/schedule_multi_page_form_test.dart": "fb0ad4e2a2235f52d23466ae4b14447c37b04d7db12deff8d6f3319a7d2f197e" + } +} diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v2.json b/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v2.json new file mode 100644 index 00000000..01688849 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v2.json @@ -0,0 +1,63 @@ +{ + "frozen_at": "2026-09-24T03:49:49.518211+00:00", + "implementation_base": "4529d9f84e911b1ee2fbb42af09698a0c27cc9f2", + "head_at_freeze": "fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f", + "previous_manifest": "/tmp/a08-source-freeze-v1.json", + "changes_since_previous": [ + "lib/data/daos/schedule_dao.dart", + "test/data/daos/schedule_dao_test.dart", + "test/data/data_sources/schedule_local_data_source_test.dart", + "test/data/repositories/schedule_aggregate_repository_test.dart" + ], + "paths": { + "docs/adr/0037-bind-schedule-save-receipts-to-local-aggregate-identities.md": "8bfef8e2fd05f0ad9a7e495475d534d5b6d5a4b0d5be33df77dcf065f3ced964", + "lib/core/backup/recurring_backup_data.dart": "875221d414768e380ec7d6f731f371840c1f39ca8efe8dc88b75949c22caf5b7", + "lib/core/database/database.dart": "c217c5ffcd37825f480c3ff303316ba2db8680d1221138fccffda7d6016f1cef", + "lib/core/database/schedule_aggregate_schema.dart": "6fcc4e2dce5a78d0cb435777782573aac03eaf1152451875267cc9c4d7f9a312", + "lib/data/daos/schedule_dao.dart": "ffddd1df540097cb68e4e6030d811d1bfc7fbeb3c5f8e1375c22305b5761f349", + "lib/data/repositories/alarm_repository_impl.dart": "bb32b840e90f68e9073f05022ff1ebac6cace18abb078afc02af62e7990de45e", + "lib/data/repositories/preparation_repository_impl.dart": "bb430d86ec37a7b3df629c44f0fa34dbd51b48102dd723d9d9d78446b56fbc80", + "lib/data/repositories/recurring_schedule_repository_impl.dart": "dbb70fd633b08d5a81619d33b1feb7938bcfffde1825bc0a53fdbe1118bf92fb", + "lib/data/repositories/schedule_aggregate_repository_impl.dart": "11df5dfdc0fb4f0c292db82edc12e789ee0bd9070cd2956f6e8c6ed2383e2e9a", + "lib/data/tables/recurring_schedule_tables.dart": "d1e76b7157a0ffbfef018ccebb28ffd1848d7cf56cebfa94a69651ea6574e3bb", + "lib/data/tables/schedules_table.dart": "33b39eb13aa513e01723da00f4914da0d6cf8d1ed07e16a0070bf9f6a800aa18", + "lib/data/tables/user_table.dart": "b21c5c98f36f7237b9d632d8afe41fff4d665235f36cc8a2e146178c9b67b046", + "lib/domain/entities/schedule_save.dart": "9fd020ed04d81f3fc8f552cf9b9437f27bd6f74159cb038bbc1590909dde3b76", + "lib/domain/repositories/schedule_aggregate_repository.dart": "3755c4135d26ee85bbfae3a19c56395a2e444f6cf6c89cb335d23b3af3df5e61", + "lib/domain/use-cases/create_schedule_form_submission_use_case.dart": "839b370b1ee876612a761848d2fb038c6f83741d520de05c9ca9e4c0ddec72c8", + "lib/domain/use-cases/load_schedule_form_draft_use_case.dart": "0cc6587d76d5fb0194b7bf47cb21169e26bb043fc9a1fd8bd72da8fbbc37ff34", + "lib/domain/use-cases/recurring_schedules_use_case.dart": "9384895177fe325a067f42388b59a704dca3966aa932de84346fb94d90ff317f", + "lib/domain/use-cases/schedule_form_submission.dart": "3b05cab96ec691388eac5007f1791be25c88ef3cacfd34a2bb19c6fa69514d79", + "lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart": "c57bd8d8fc3204e920f677925d2ce5f1f6953062d38bef41c2cb1c51f465b6b5", + "lib/domain/use-cases/schedule_save_workflow.dart": "3eb8dcbbe095a31a972c95a56360b8275082b5a07a0e946d6d05c931ff971477", + "lib/domain/use-cases/update_schedule_form_submission_use_case.dart": "975215ea2d3e869b3da5c9e5c3acb643a79f869f18f81fa37289ac4ae5a8a4fa", + "lib/l10n/app_en.arb": "213a419f55776010ab7f980b83b19b56e13580d6a7c7c229eb692aea7e5f93bf", + "lib/l10n/app_ko.arb": "48b5c1fd482ac93d403b21c5e218e6645cb8cb55f36ddf8c99ca90f850252db0", + "lib/l10n/app_localizations.dart": "7298fb334a699579aaac77fc05dd354f32ba9bd714968f8cf03fe403a0318ec6", + "lib/l10n/app_localizations_en.dart": "14c3741c7e7307f61673d994ad5e44cd742a5f5e26b80fd9783225286ecee827", + "lib/l10n/app_localizations_ko.dart": "660c8d17c39a7e305519876053aa4dbf6a945f8ad3b03ef4ce13abddf7847c25", + "lib/presentation/schedule_create/bloc/schedule_form_bloc.dart": "6642f6ad29d36434ecb3dbb879d09b6f2a30759c529f0d9ea5cdb68e28e5a69a", + "lib/presentation/schedule_create/bloc/schedule_form_event.dart": "9f7a765946c18aea23d8fb97917ce100d6f40bc2d52bd63027a3d472cd086b56", + "lib/presentation/schedule_create/bloc/schedule_form_state.dart": "b692aa1667462f547662c2c57beab1b2b8b70dd04a893e432761ce96f0b64f7c", + "lib/presentation/schedule_create/components/schedule_multi_page_form.dart": "10a04857519473eb7c83fa6beb5958d3c62fa6b3139147105d8d49a860b8453a", + "pubspec.lock": "1305211851a74cdea3bae6402409b9ac0108562751859b062bb90aeb34900d4f", + "pubspec.yaml": "71d14bb6731407309b150f89e548ec4d78fa0843087e1b2a8d1489425697eaf1", + "test/core/backup/backup_service_test.dart": "92d76777b8cc5a4205fa33075281997313cf50f99b38271edd4e16718d6903f2", + "test/core/database/database_index_test.dart": "ead0ee32b368a911832fb1e08e86ec684eb767949d7ee060022ad6c81ceec898", + "test/core/database/recurring_migration_test.dart": "03a0745c00ea247c55dea9f65b27a435b0492b217520c0f75fab4a898b672c0d", + "test/core/database/schedule_aggregate_migration_test.dart": "46bf6aa216d7e46c47730e8aa0e87216984b6551dd1ec015cfa3f7d84e619d91", + "test/data/daos/schedule_dao_test.dart": "ce01673ed2b47b6f4eadec9c6b74c68d5f933517ceeede2c609e09caaca4e056", + "test/data/data_sources/schedule_local_data_source_test.dart": "a7c1f55b98887dcf2538f608070545cc64af145f5617b290bc5589e10511a365", + "test/data/repositories/recurring_schedule_repository_test.dart": "60969859e40aaf9b5301d5c1340c6ff201bb95466b50ce1369a5677f751cec05", + "test/data/repositories/schedule_aggregate_repository_test.dart": "9e7e9c18cb9358e2282e6c129c2e04f165c9602006410737043670aefd844a97", + "test/data/repositories/schedule_aggregate_snapshot_test.dart": "50531d54e9eed6b93414185c62326f5cd6489c46eaf543bb08848511518efbca", + "test/domain/use-cases/create_schedule_form_submission_use_case_test.dart": "317a45914cb0490bb54fe25cfbdbb213ff0a95697820834774dd2936f88d6e67", + "test/domain/use-cases/update_schedule_form_submission_use_case_test.dart": "5fd739f2cbaada8a787d14d1af8dd7b9718df86027d356919ed7669b5f78b6a9", + "test/fixtures/database/README.md": "1e4068750433db1b18c5936216786128def1088f532d5a9486d9e9405625f168", + "test/fixtures/database/schema_v1.sql": "782cd3a3cfdca56c67f896a7fbdfa9791a349f77b2b65e21c7f0c0f2ce4a121d", + "test/fixtures/database/schema_v2.sql": "a7ec3c57f1c86a58ef45c3417abe187fa4e44a0be87e3861a7182cb3ab02d7c5", + "test/presentation/recurring/recurring_form_integration_test.dart": "6d935c38090713b0a729f8fe70a7ff4ff018d4de0f8fa508ab201040988422c9", + "test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart": "15bffe233e663f55d6588533dd272ccdd8658ef11be709e3708c8241cf18c954", + "test/presentation/schedule_create/components/schedule_multi_page_form_test.dart": "fb0ad4e2a2235f52d23466ae4b14447c37b04d7db12deff8d6f3319a7d2f197e" + } +} diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v3.json b/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v3.json new file mode 100644 index 00000000..8dd44de0 --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze-v3.json @@ -0,0 +1,61 @@ +{ + "frozen_at": "2026-09-24T03:52:27.662338+00:00", + "implementation_base": "4529d9f84e911b1ee2fbb42af09698a0c27cc9f2", + "head_at_freeze": "3ba88146ee4b4a5807b949e8ea5104ff3ca86448", + "previous_manifest": "/tmp/a08-source-freeze-v2.json", + "changes_since_previous": [ + "lib/data/daos/schedule_dao.dart", + "test/data/daos/schedule_dao_test.dart" + ], + "paths": { + "docs/adr/0037-bind-schedule-save-receipts-to-local-aggregate-identities.md": "8bfef8e2fd05f0ad9a7e495475d534d5b6d5a4b0d5be33df77dcf065f3ced964", + "lib/core/backup/recurring_backup_data.dart": "875221d414768e380ec7d6f731f371840c1f39ca8efe8dc88b75949c22caf5b7", + "lib/core/database/database.dart": "c217c5ffcd37825f480c3ff303316ba2db8680d1221138fccffda7d6016f1cef", + "lib/core/database/schedule_aggregate_schema.dart": "6fcc4e2dce5a78d0cb435777782573aac03eaf1152451875267cc9c4d7f9a312", + "lib/data/daos/schedule_dao.dart": "370e4aa68d2159c5e8a24f5b86e704b19758c3365903e6cb1045b5555610e738", + "lib/data/repositories/alarm_repository_impl.dart": "bb32b840e90f68e9073f05022ff1ebac6cace18abb078afc02af62e7990de45e", + "lib/data/repositories/preparation_repository_impl.dart": "bb430d86ec37a7b3df629c44f0fa34dbd51b48102dd723d9d9d78446b56fbc80", + "lib/data/repositories/recurring_schedule_repository_impl.dart": "dbb70fd633b08d5a81619d33b1feb7938bcfffde1825bc0a53fdbe1118bf92fb", + "lib/data/repositories/schedule_aggregate_repository_impl.dart": "11df5dfdc0fb4f0c292db82edc12e789ee0bd9070cd2956f6e8c6ed2383e2e9a", + "lib/data/tables/recurring_schedule_tables.dart": "d1e76b7157a0ffbfef018ccebb28ffd1848d7cf56cebfa94a69651ea6574e3bb", + "lib/data/tables/schedules_table.dart": "33b39eb13aa513e01723da00f4914da0d6cf8d1ed07e16a0070bf9f6a800aa18", + "lib/data/tables/user_table.dart": "b21c5c98f36f7237b9d632d8afe41fff4d665235f36cc8a2e146178c9b67b046", + "lib/domain/entities/schedule_save.dart": "9fd020ed04d81f3fc8f552cf9b9437f27bd6f74159cb038bbc1590909dde3b76", + "lib/domain/repositories/schedule_aggregate_repository.dart": "3755c4135d26ee85bbfae3a19c56395a2e444f6cf6c89cb335d23b3af3df5e61", + "lib/domain/use-cases/create_schedule_form_submission_use_case.dart": "839b370b1ee876612a761848d2fb038c6f83741d520de05c9ca9e4c0ddec72c8", + "lib/domain/use-cases/load_schedule_form_draft_use_case.dart": "0cc6587d76d5fb0194b7bf47cb21169e26bb043fc9a1fd8bd72da8fbbc37ff34", + "lib/domain/use-cases/recurring_schedules_use_case.dart": "9384895177fe325a067f42388b59a704dca3966aa932de84346fb94d90ff317f", + "lib/domain/use-cases/schedule_form_submission.dart": "3b05cab96ec691388eac5007f1791be25c88ef3cacfd34a2bb19c6fa69514d79", + "lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart": "c57bd8d8fc3204e920f677925d2ce5f1f6953062d38bef41c2cb1c51f465b6b5", + "lib/domain/use-cases/schedule_save_workflow.dart": "3eb8dcbbe095a31a972c95a56360b8275082b5a07a0e946d6d05c931ff971477", + "lib/domain/use-cases/update_schedule_form_submission_use_case.dart": "975215ea2d3e869b3da5c9e5c3acb643a79f869f18f81fa37289ac4ae5a8a4fa", + "lib/l10n/app_en.arb": "213a419f55776010ab7f980b83b19b56e13580d6a7c7c229eb692aea7e5f93bf", + "lib/l10n/app_ko.arb": "48b5c1fd482ac93d403b21c5e218e6645cb8cb55f36ddf8c99ca90f850252db0", + "lib/l10n/app_localizations.dart": "7298fb334a699579aaac77fc05dd354f32ba9bd714968f8cf03fe403a0318ec6", + "lib/l10n/app_localizations_en.dart": "14c3741c7e7307f61673d994ad5e44cd742a5f5e26b80fd9783225286ecee827", + "lib/l10n/app_localizations_ko.dart": "660c8d17c39a7e305519876053aa4dbf6a945f8ad3b03ef4ce13abddf7847c25", + "lib/presentation/schedule_create/bloc/schedule_form_bloc.dart": "6642f6ad29d36434ecb3dbb879d09b6f2a30759c529f0d9ea5cdb68e28e5a69a", + "lib/presentation/schedule_create/bloc/schedule_form_event.dart": "9f7a765946c18aea23d8fb97917ce100d6f40bc2d52bd63027a3d472cd086b56", + "lib/presentation/schedule_create/bloc/schedule_form_state.dart": "b692aa1667462f547662c2c57beab1b2b8b70dd04a893e432761ce96f0b64f7c", + "lib/presentation/schedule_create/components/schedule_multi_page_form.dart": "10a04857519473eb7c83fa6beb5958d3c62fa6b3139147105d8d49a860b8453a", + "pubspec.lock": "1305211851a74cdea3bae6402409b9ac0108562751859b062bb90aeb34900d4f", + "pubspec.yaml": "71d14bb6731407309b150f89e548ec4d78fa0843087e1b2a8d1489425697eaf1", + "test/core/backup/backup_service_test.dart": "92d76777b8cc5a4205fa33075281997313cf50f99b38271edd4e16718d6903f2", + "test/core/database/database_index_test.dart": "ead0ee32b368a911832fb1e08e86ec684eb767949d7ee060022ad6c81ceec898", + "test/core/database/recurring_migration_test.dart": "03a0745c00ea247c55dea9f65b27a435b0492b217520c0f75fab4a898b672c0d", + "test/core/database/schedule_aggregate_migration_test.dart": "46bf6aa216d7e46c47730e8aa0e87216984b6551dd1ec015cfa3f7d84e619d91", + "test/data/daos/schedule_dao_test.dart": "bc500cd4ebd497379c306ae2579a8619acab9795a580e73759e968a37707acfb", + "test/data/data_sources/schedule_local_data_source_test.dart": "a7c1f55b98887dcf2538f608070545cc64af145f5617b290bc5589e10511a365", + "test/data/repositories/recurring_schedule_repository_test.dart": "60969859e40aaf9b5301d5c1340c6ff201bb95466b50ce1369a5677f751cec05", + "test/data/repositories/schedule_aggregate_repository_test.dart": "9e7e9c18cb9358e2282e6c129c2e04f165c9602006410737043670aefd844a97", + "test/data/repositories/schedule_aggregate_snapshot_test.dart": "50531d54e9eed6b93414185c62326f5cd6489c46eaf543bb08848511518efbca", + "test/domain/use-cases/create_schedule_form_submission_use_case_test.dart": "317a45914cb0490bb54fe25cfbdbb213ff0a95697820834774dd2936f88d6e67", + "test/domain/use-cases/update_schedule_form_submission_use_case_test.dart": "5fd739f2cbaada8a787d14d1af8dd7b9718df86027d356919ed7669b5f78b6a9", + "test/fixtures/database/README.md": "1e4068750433db1b18c5936216786128def1088f532d5a9486d9e9405625f168", + "test/fixtures/database/schema_v1.sql": "782cd3a3cfdca56c67f896a7fbdfa9791a349f77b2b65e21c7f0c0f2ce4a121d", + "test/fixtures/database/schema_v2.sql": "a7ec3c57f1c86a58ef45c3417abe187fa4e44a0be87e3861a7182cb3ab02d7c5", + "test/presentation/recurring/recurring_form_integration_test.dart": "6d935c38090713b0a729f8fe70a7ff4ff018d4de0f8fa508ab201040988422c9", + "test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart": "15bffe233e663f55d6588533dd272ccdd8658ef11be709e3708c8241cf18c954", + "test/presentation/schedule_create/components/schedule_multi_page_form_test.dart": "fb0ad4e2a2235f52d23466ae4b14447c37b04d7db12deff8d6f3319a7d2f197e" + } +} diff --git a/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze.json b/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze.json new file mode 100644 index 00000000..4fe550db --- /dev/null +++ b/plans/audit-2026-09-23/artifacts/a08/a08-source-freeze.json @@ -0,0 +1,61 @@ +{ + "frozen_at": "2026-09-24T03:56:43.197320+00:00", + "implementation_base": "4529d9f84e911b1ee2fbb42af09698a0c27cc9f2", + "head_at_freeze": "3ba88146ee4b4a5807b949e8ea5104ff3ca86448", + "previous_manifest": "/tmp/a08-source-freeze-v3.json", + "changes_since_previous": [ + "test/data/repositories/schedule_aggregate_repository_test.dart" + ], + "paths": { + "docs/adr/0037-bind-schedule-save-receipts-to-local-aggregate-identities.md": "8bfef8e2fd05f0ad9a7e495475d534d5b6d5a4b0d5be33df77dcf065f3ced964", + "lib/core/backup/recurring_backup_data.dart": "875221d414768e380ec7d6f731f371840c1f39ca8efe8dc88b75949c22caf5b7", + "lib/core/database/database.dart": "c217c5ffcd37825f480c3ff303316ba2db8680d1221138fccffda7d6016f1cef", + "lib/core/database/schedule_aggregate_schema.dart": "6fcc4e2dce5a78d0cb435777782573aac03eaf1152451875267cc9c4d7f9a312", + "lib/data/daos/schedule_dao.dart": "370e4aa68d2159c5e8a24f5b86e704b19758c3365903e6cb1045b5555610e738", + "lib/data/repositories/alarm_repository_impl.dart": "bb32b840e90f68e9073f05022ff1ebac6cace18abb078afc02af62e7990de45e", + "lib/data/repositories/preparation_repository_impl.dart": "bb430d86ec37a7b3df629c44f0fa34dbd51b48102dd723d9d9d78446b56fbc80", + "lib/data/repositories/recurring_schedule_repository_impl.dart": "dbb70fd633b08d5a81619d33b1feb7938bcfffde1825bc0a53fdbe1118bf92fb", + "lib/data/repositories/schedule_aggregate_repository_impl.dart": "11df5dfdc0fb4f0c292db82edc12e789ee0bd9070cd2956f6e8c6ed2383e2e9a", + "lib/data/tables/recurring_schedule_tables.dart": "d1e76b7157a0ffbfef018ccebb28ffd1848d7cf56cebfa94a69651ea6574e3bb", + "lib/data/tables/schedules_table.dart": "33b39eb13aa513e01723da00f4914da0d6cf8d1ed07e16a0070bf9f6a800aa18", + "lib/data/tables/user_table.dart": "b21c5c98f36f7237b9d632d8afe41fff4d665235f36cc8a2e146178c9b67b046", + "lib/domain/entities/schedule_save.dart": "9fd020ed04d81f3fc8f552cf9b9437f27bd6f74159cb038bbc1590909dde3b76", + "lib/domain/repositories/schedule_aggregate_repository.dart": "3755c4135d26ee85bbfae3a19c56395a2e444f6cf6c89cb335d23b3af3df5e61", + "lib/domain/use-cases/create_schedule_form_submission_use_case.dart": "839b370b1ee876612a761848d2fb038c6f83741d520de05c9ca9e4c0ddec72c8", + "lib/domain/use-cases/load_schedule_form_draft_use_case.dart": "0cc6587d76d5fb0194b7bf47cb21169e26bb043fc9a1fd8bd72da8fbbc37ff34", + "lib/domain/use-cases/recurring_schedules_use_case.dart": "9384895177fe325a067f42388b59a704dca3966aa932de84346fb94d90ff317f", + "lib/domain/use-cases/schedule_form_submission.dart": "3b05cab96ec691388eac5007f1791be25c88ef3cacfd34a2bb19c6fa69514d79", + "lib/domain/use-cases/schedule_mutation_alarm_effects_coordinator.dart": "c57bd8d8fc3204e920f677925d2ce5f1f6953062d38bef41c2cb1c51f465b6b5", + "lib/domain/use-cases/schedule_save_workflow.dart": "3eb8dcbbe095a31a972c95a56360b8275082b5a07a0e946d6d05c931ff971477", + "lib/domain/use-cases/update_schedule_form_submission_use_case.dart": "975215ea2d3e869b3da5c9e5c3acb643a79f869f18f81fa37289ac4ae5a8a4fa", + "lib/l10n/app_en.arb": "213a419f55776010ab7f980b83b19b56e13580d6a7c7c229eb692aea7e5f93bf", + "lib/l10n/app_ko.arb": "48b5c1fd482ac93d403b21c5e218e6645cb8cb55f36ddf8c99ca90f850252db0", + "lib/l10n/app_localizations.dart": "7298fb334a699579aaac77fc05dd354f32ba9bd714968f8cf03fe403a0318ec6", + "lib/l10n/app_localizations_en.dart": "14c3741c7e7307f61673d994ad5e44cd742a5f5e26b80fd9783225286ecee827", + "lib/l10n/app_localizations_ko.dart": "660c8d17c39a7e305519876053aa4dbf6a945f8ad3b03ef4ce13abddf7847c25", + "lib/presentation/schedule_create/bloc/schedule_form_bloc.dart": "6642f6ad29d36434ecb3dbb879d09b6f2a30759c529f0d9ea5cdb68e28e5a69a", + "lib/presentation/schedule_create/bloc/schedule_form_event.dart": "9f7a765946c18aea23d8fb97917ce100d6f40bc2d52bd63027a3d472cd086b56", + "lib/presentation/schedule_create/bloc/schedule_form_state.dart": "b692aa1667462f547662c2c57beab1b2b8b70dd04a893e432761ce96f0b64f7c", + "lib/presentation/schedule_create/components/schedule_multi_page_form.dart": "10a04857519473eb7c83fa6beb5958d3c62fa6b3139147105d8d49a860b8453a", + "pubspec.lock": "1305211851a74cdea3bae6402409b9ac0108562751859b062bb90aeb34900d4f", + "pubspec.yaml": "71d14bb6731407309b150f89e548ec4d78fa0843087e1b2a8d1489425697eaf1", + "test/core/backup/backup_service_test.dart": "92d76777b8cc5a4205fa33075281997313cf50f99b38271edd4e16718d6903f2", + "test/core/database/database_index_test.dart": "ead0ee32b368a911832fb1e08e86ec684eb767949d7ee060022ad6c81ceec898", + "test/core/database/recurring_migration_test.dart": "03a0745c00ea247c55dea9f65b27a435b0492b217520c0f75fab4a898b672c0d", + "test/core/database/schedule_aggregate_migration_test.dart": "46bf6aa216d7e46c47730e8aa0e87216984b6551dd1ec015cfa3f7d84e619d91", + "test/data/daos/schedule_dao_test.dart": "bc500cd4ebd497379c306ae2579a8619acab9795a580e73759e968a37707acfb", + "test/data/data_sources/schedule_local_data_source_test.dart": "a7c1f55b98887dcf2538f608070545cc64af145f5617b290bc5589e10511a365", + "test/data/repositories/recurring_schedule_repository_test.dart": "60969859e40aaf9b5301d5c1340c6ff201bb95466b50ce1369a5677f751cec05", + "test/data/repositories/schedule_aggregate_repository_test.dart": "dfa8ce81dd9f8f54fd3bdcd00354c88c921d37c615684fdf984942e47a2be763", + "test/data/repositories/schedule_aggregate_snapshot_test.dart": "50531d54e9eed6b93414185c62326f5cd6489c46eaf543bb08848511518efbca", + "test/domain/use-cases/create_schedule_form_submission_use_case_test.dart": "317a45914cb0490bb54fe25cfbdbb213ff0a95697820834774dd2936f88d6e67", + "test/domain/use-cases/update_schedule_form_submission_use_case_test.dart": "5fd739f2cbaada8a787d14d1af8dd7b9718df86027d356919ed7669b5f78b6a9", + "test/fixtures/database/README.md": "1e4068750433db1b18c5936216786128def1088f532d5a9486d9e9405625f168", + "test/fixtures/database/schema_v1.sql": "782cd3a3cfdca56c67f896a7fbdfa9791a349f77b2b65e21c7f0c0f2ce4a121d", + "test/fixtures/database/schema_v2.sql": "a7ec3c57f1c86a58ef45c3417abe187fa4e44a0be87e3861a7182cb3ab02d7c5", + "test/presentation/recurring/recurring_form_integration_test.dart": "6d935c38090713b0a729f8fe70a7ff4ff018d4de0f8fa508ab201040988422c9", + "test/presentation/schedule_create/bloc/schedule_form_bloc_test.dart": "15bffe233e663f55d6588533dd272ccdd8658ef11be709e3708c8241cf18c954", + "test/presentation/schedule_create/components/schedule_multi_page_form_test.dart": "fb0ad4e2a2235f52d23466ae4b14447c37b04d7db12deff8d6f3319a7d2f197e" + }, + "final_test_only_delta": true +} diff --git a/plans/audit-2026-09-23/artifacts/a08/en-delivery-pending.png b/plans/audit-2026-09-23/artifacts/a08/en-delivery-pending.png new file mode 100644 index 0000000000000000000000000000000000000000..5fd214c196cb1976287cedc356aac53a91afdeee GIT binary patch literal 66168 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 literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/artifacts/a08/en-rollback.png b/plans/audit-2026-09-23/artifacts/a08/en-rollback.png new file mode 100644 index 0000000000000000000000000000000000000000..3420c640ea32ed091786ef2af4c66909bec667c3 GIT binary patch literal 63907 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 rcU2s~RBq90#s1c< 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 literal 0 HcmV?d00001 diff --git a/plans/audit-2026-09-23/issues/A08.md b/plans/audit-2026-09-23/issues/A08.md index 07fb0f09..5887486b 100644 --- a/plans/audit-2026-09-23/issues/A08.md +++ b/plans/audit-2026-09-23/issues/A08.md @@ -180,3 +180,126 @@ **root 답변** > Q4 포함확정. writeratomic만으로reader여러await가snapshot되는게아니므로 planning/edit기준aggregate read는동일DBsnapshot/일관readport. materialize기존tx완료후immutable snapshot,OSawait는밖. actualDBold전체/new전체둘중하나임을중간commit경합테스트로증명, fake일관성만으로대체금지. 수동preparationstream을join진실원본으로쓰지않음. query성능N+1최적화범위(C03)와는분리하되정합성위해필요한batchread는허용. 네문답과모든추가근거본문동결알려주세요. + + +## 2026-09-24 구현 및 로컬 검증 결과 + +구현 commit: `126161dab042365c89983934b1673d860c27d8d3`. 49개 source/test/fixture/ADR 파일의 동결 SHA256과 커밋 blob을 모두 다시 대조했다. 일정·장소·준비·revision을 하나의 transaction으로 저장하고 commit 후 알림 effects를 실행한다. 기존 source에서는 준비 INSERT 실패 후 일정 행이 남는 결함을 실제 DB로 재현했다. + +- FormSubmission을 aggregate repository + ScheduleSaveWorkflow로 연결했다. 편집/신규 초안/알림 계획/백업 reader의 일관된 snapshot, shared 장소 복사, 실제 소유한 준비 정의만 정리하는 경계를 검증했다. +- schema 3에 설치 store incarnation, aggregate identity/version 및 마지막 intent receipt를 둔다. 사용자 백업 format 2와 legacy 1은 유지하며 설치 한정 receipt를 내보내지 않는다. ABA/다른 편집/삭제 후 재생성/복원에 old intent가 성공하지 않는다. 새 생성은 captured durable revision도 검사하므로 무관한 편집에도 보수적인 충돌이 가능하다. +- 반복 series root는 영속 rootSegmentId로 유지하며 split으로 원 occurrence가 사라진 뒤에도 file DB close/reopen 후 같은 의도를 판별한다. 실제 회차 60개 생성 후 5개 추가 materialization은 사용자 수정 revision/receipt를 바꾸지 않는다. +- DB 반영 후 알림 미확인은 typed partial receipt로 표시하며 재시도는 DB를 다시 저장하지 않는다. 같은 Bloc의 새 form과 늦은 모달/저장/effects callback은 opaque owner와 mutation/current-route로 구별한다. +- legacy DAO가 오래된 row의 동시성 metadata를 덮지 않게 했고 AFTER trigger 이후 같은 transaction에서 read-back한다. watch는 단일 SQL snapshot + 의존 테이블 invalidation + 기존 시간 오름차순을 보존한다. + +검증: 최종 제품 바이트와 동일한 v3 freeze에서 전체 **1,018개 통과**, coverage **88.96% (14,113/15,864)**. 이후 제품 변경 없이 회차 추가 회귀 하나를 강화했고 최종 파일에서 **41개 집중 + 별도 실제 DB probe 1개** 및 analyzer 통과. 따라서 최종 테스트 소스 전체 실행은 아직 원격 CI로 확인할 예정이다. generated/local-only 정책, Python11, Android source manifest, diff check도 통과했다. 최초 전체 실행의 1,008 pass/7 fail 및 중단된 중간 실행은 보존하고 성공 근거로 사용하지 않는다. + +KO/EN 430×932·200% 합성 widget 캡처 4장을 root가 확인했다. 실제 DB fault rollback·초안 보존·1회 commit 재시도·알림 전용 재시도는 widget/DB 테스트로 연결했다. 전체 form 접근성/실기기 알림 검증이 아니며 배경 폼의 영어 label 줄바꿈은 U08 범위다. + +역사 migration: schema2는 baseline4529d9f8의 실제 Drift DDL dump, schema1은 역사 ccc6272b 소스 차이로 재구성했다. native SQLite 파일의 v1/v2→3·rollback·future-version 거부를 검증했으며 과거 released binary 실행 또는 모바일 SQLCipher 증거로 주장하지 않는다. + +남은 조건: A09/D02는 모든 데이터 replacement(프로필 보존 방식 포함)에서 store incarnation을 반드시 교체해야 한다. C01의 검증된 staging/runtime 교체는 A09, 손상 저장소 복구는 D02, 모바일 SQLCipher/OS 전달·실제 파일 provider는 해당 기기 검증이 남았다. 원격 CI·병합·출시는 이 로컬 결과와 별도다. 이슈는 OPEN 유지한다. + +검증 manifest와 모든 로그·원래 실패·fixture provenance·캡처·전체 문답: `plans/audit-2026-09-23/a08-final-validation.json`, `plans/audit-2026-09-23/artifacts/a08/`. + +### 구현 중 실제 추가 질문·답변·리뷰 전체 + +아래는 설계 당시 원문을 대체하지 않고 추가한다. 실제 조사로 철회하거나 보완한 판단과 실패 이력도 그대로 보존한다. + +# A08 implementation review transcript + +## Q1 — 최소 durable receipt 및 schema + +Agent: A08 구현 전 핵심 결정 Q1: 현재 schema 2에는 aggregate version/receipt가 없어 앱 재시작 후 같은 저장 의도와 ABA를 구별할 durable 근거가 없습니다. 추천은 전역 ledger 대신 Schedules와 RecurringScheduleSegments에 최소한의 incarnation/version 및 마지막 mutation ID+입력 digest를 두고 schema 3으로 migration하는 것입니다. 한 intent는 draft에서 stable UUID를 유지하고, 같은 receipt+입력일 때만 no-op이며 다른 편집/삭제·재생성 뒤에는 충돌합니다. backup v2는 사용자 콘텐츠 형식을 유지하고 이 설치 한정 receipt/version은 내보내지 않으며 restore 때 새 incarnation을 부여해 옛 intent가 통과하지 않게 하겠습니다. v1/v2 DB migration 및 backup v1/v2 compatibility를 A08 실제 테스트에 포함합니다. 이 schema 3 / backup 형식 유지 방향을 승인할까요? 구체 계획은 atomic aggregate repository → 일관된 edit/planning read → 기존 recurring tx를 감싸 receipt/protection → FormSubmission typed durable/effects 결과 및 실제 UI → actual Drift rollback/재시도/ABA/reader 검증 순서입니다. + +Root: Q1 방향 채택합니다. A08 합의에 schema 변경 필요 시 migration/backup 호환 검증 포함이 명시돼 있으므로 schema3로 최소 aggregate incarnation/version/last mutation ID+정규화 입력 digest를 추가하세요. 전역 ledger 없이 해당 aggregate 소유 테이블에 한정하며, schedule과 recurring segment가 각각 어떤 저장 의도의 권위인지/this-and-future split에서 receipt가 어디에 남는지 먼저 명확히 정리하세요. 같은 mutation identity+input+incarnation과 durable receipt가 실제 현재 aggregate에 대응할 때만 재시도 성공; 내용 같음만으로 과거 성공 추측 금지, 중간 편집·ABA·복원 후 old intent는 conflict. 원 생성 intent 뒤 부수적 materialization이 version을 어떻게 다루는지도 테스트해야 합니다. backup v2 형식 유지 및 설치 한정 receipt 제외 채택; restore에서 새 incarnation 부여하고 legacy v1/v2도 지원. v1→3/v2→3 actual SQLite migration 원자성·기존 사용자 필드/정렬/시작완료보호 보존, 신규 DB schema 대조, 미래버전 거부 회귀를 포함하세요. strict local-only 및 ignored generated 유지. 실제 추가 질문/답 전체 transcript 보존. 상세 반복 receipt 설계에서 판단이 필요하면 다음 질문으로 보내주세요. + +## Q2 — 반복 receipt 및 삭제 후 생성 재시도 + +Agent: A08 Q2 — 반복 receipt 권위는 series 최초 segment(분할 후에도 유지되는 root segment)로 두고, 이번 회차 저장은 해당 Schedule receipt + series root version을 함께 갱신, 이후 회차 저장은 기존 root segment receipt에 남겨 원 회차가 split 과정에서 삭제돼도 재시도를 확인하는 방향을 추천합니다. 새 회차 materialization 자체는 사용자 변경 version을 올리지 않되 기존 회차의 실제 편집/시작/완료/삭제 및 segment 변경은 version을 바꾸고 stale baseline을 거절합니다. 추가로 전역 tombstone 없이 '생성 성공→삭제→오래된 생성 intent 재전송'에서 행 자체가 사라지면 이전 생성과 최초 생성을 구별할 수 없습니다. 추천은 draft 생성 시 원래 target absence와 설치 generation 및 현재 durable revision을 생성용 baseline으로 잡고, ID가 없는 create의 첫 commit만 그 revision 확인을 요구하는 것입니다(편집은 aggregate version만 사용하여 무관 편집 충돌 없음). 이미 존재하는 동일 receipt 재시도는 current version으로 허용하고, 삭제 뒤 revision이 변한 old create는 거부합니다. 이는 새 생성 초안 동안 무관한 durable 변경이 생기면 재검토가 필요하다는 보수적 trade-off입니다. tombstone/전역 ledger를 추가하지 않고 이 생성 한정 revision fence를 채택할까요? + +Root: Q2 채택하되 경계 명확히 해주세요. 반복 create/이후 회차 변경 receipt 권위는 유지되는 최초 root segment, 이번 회차는 Schedule receipt와 root version을 같은 transaction으로 갱신. materialization만으로 사용자 변경 version을 올리지 않지만 기존 영향 회차의 편집/시작/완료/삭제 및 segment 변경은 stale 근거에 반영. split 후 원 occurrence가 없어도 성공 receipt를 root로 검증하고 root 삭제/재생성은 incarnation으로 구별. 생성 한정으로 absent target+durable revision+설치 generation baseline을 캡처해 최초 insert의 revision fence를 채택합니다. 무관 durable 변경으로 생성 충돌이 생기는 보수적 trade-off는 문서와 UI에 명확히: 초안 보존, 현재 상태 재확인 후 사용자가 새 저장 의도를 제출, 자동 baseline갱신/조용한 override 금지. edit에는 globalrevision fence를 적용하지 않습니다. 프로세스 재시작 때 in-memory generation이 0으로 돌아가도 old create/restore 후 intent가 통과하지 않도록 durable revision의 단조성 또는 설치 식별 근거를 실제 확인하세요. 기존 A04/restore monotonic 계약 재사용, 같은revision으로 복원해 우회될 수 있다면 추가로 질문. 실제 테스트에 create→delete→old retry, split후 lostreceipt/restart, unrelated edit→create conflict/draft preservation/new explicit intent를 포함하세요. + +## Restore revision assumption correction + +Root: root 소스 확인: 현재 BackupService._applyRestore L242는 dataRevision: Value(data.dataRevision + 1)로 백업 revision+1을 넣습니다. 따라서 현 restore가 설치의 이전 revision보다 단조 증가한다는 가정을 사용하면 안 됩니다. 생성용 revision fence만으로 process restart/restore 뒤 stale intent 안전성을 증명하기 부족합니다. 최소 durable installation/store incarnation을 유지/교체하는 설계 또는 restore revision 계약 조정이 필요할 수 있으니 실제 대안을 조사하고 질문해주세요. 단순 max(active,backup)+1로 바꾸더라도 reset/새 DB의 revision 재사용과 detached persisted draft의 관계까지 검토. A09 담당 전체 protocol을 지금 재작성하지 말고 A08 receipt가 새 store에서 재사용되지 않는 최소 경계를 확보하세요. + +Agent: Q3 — 지적하신 대로 restore는 backup revision+1로 재설정하므로 monotonic 가정은 철회합니다. 최소 대안으로 Users(Local Profile)에 설치 DB incarnation 1개를 schema3 additive column으로 추가하고 migration 때 기존 profile에 한 번 발급, 정상 재시작 때 유지, restore/reset의 새 profile INSERT 때 새 UUID를 발급하겠습니다. backup payload에는 포함하지 않습니다. 모든 draft/receipt는 이 store incarnation과 aggregate incarnation을 묶어 검증하여 restart에서 in-memory generation=0 및 같은 revision이어도 old intent를 거부합니다. 전역 ledger/새 replacement protocol 없이 기존 profile 생성·restore insert 경계만 사용하고, 미래 A09가 profile row를 보존하는 교체로 바꾸면 incarnation 재발급을 필수 계약으로 넘깁니다. 이 최소 durable store identity를 채택할까요? + +Root: Q3 채택합니다. Users의 additive store incarnation은 기존 profile에 migration 시 한 번 발급하고 정상 재시작/부분 설정·점수 UPDATE·기존 profile upsert에서 보존합니다. restore/reset의 새 profile 생성은 새 UUID, backup payload에는 제외. draft/receipt는 store+aggregate incarnation을 묶어 확인하므로 같은 revision/in-memory generation=0을 새 store 재사용 근거로 삼지 않습니다. profile entity mapper가 기존 upsert 시 default UUID를 덮는 A04 유사 회귀를 특히 검사하세요. migration 중 임시 빈/default 값이 runtime에 보이지 않고 기존/신규 row 모두 유효 identity인지 actualDB 검증. v1/v2backup import/export roundtrip에서 내부 identity가 유출/복원되지 않는지 확인. A09/D02의 profile-preserving 교체도 incarnation 재발급해야 한다는 통합 계약을 handoff에 남기세요. 이 결정은 영속 schema 변경과 보수적 create 충돌이라는 trade-off가 있어 skill ADR 양식으로 짧은 ADR 작성이 적절합니다. CONTEXT에는 구현 세부를 추가하지 마세요. 실제 monotonic 가정 철회/정정 문답 모두 보존해주세요. + +## Historical fixture review + +Root: 검증 주의: 현 test/core/database/recurring_migration_test.dart는 현재 schema를 만든 뒤 additive 열/테이블을 제거해 v1으로 낮추는 fixture입니다. schema3 migration 검증에서 새 모델로 old schema를 재구성한 테스트만으로 역사 v1/v2 호환을 주장하지 마세요. baseline4529d9f8의 실제 SQL/table 정의에서 frozen v2 fixture, v1 역사 정의를 확보하고 사용자 필드/started/finished/recurring 및 row identity 보존·실패 rollback·미래버전 거부를 실제 open 경로로 검증. D04 전체 SQLCipher/device 항목은 별도지만 이번schema변경의 호환성은 A08에서 완료해야 합니다. root는 지금 Flutter 실행하지 않습니다. + +Agent: A08 수정 전 actual Drift 회귀가 실제 실패했습니다(/tmp/a08-negative.log): 준비 INSERT fault 후 일정 row가 이미 남아 전체 rollback 조건을 위반했습니다. 현재 최소 schema3 identity/receipt와 aggregate repository를 구현 중입니다. Store identity는 profile insert trigger에서 발급해 기존 mapper/upsert가 UUID를 덮지 않게 했고, legacy writer까지 aggregate 변경을 관측하도록 제한된 SQL version trigger를 사용합니다. v1/v2 historical fixture는 baseline의 실제 DDL로 고정하여 migration 테스트하겠습니다. Flutter 독점 사용 중이며 아직 freeze/최종 검증 단계는 아닙니다. + +## Stable root, dependency version, normalized input and field ownership review + +Root: root WIP 리뷰(구현 중임 인지): 1) _root/SQLrootFor가 created_at,id 최소값으로 권위를 매번 고르면 시스템 시계 역행·동일timestamp/split 새 UUID 순서로 root가 바뀔 수 있습니다. root 소유권은 영속 안정 identity로 고정하거나 동등불변성 증명 필요. 2) template를 실제 preparationFor에서 실시간 읽는데 trigger는 preparation_template_steps/템플릿 삭제·수정이 연결 aggregate version을 갱신하지 않습니다. 편집초안 이후 참조 template 변경/삭제 또는 definition 변화, nullable/legacy emptycustom defaultfallback도 stale 근거에 반영해야 합니다. 모든상태를 무조건 globalrevision로 막지 말고 의존중인 aggregate만. 3) _steps digest .inMinutes는 UI 분단위정책이라면 저장validator도 정수분을 강제해야 합니다. 60초/119초가 같은 digest로 다른 intent내용을 동일 성공 처리하는 경계 방지(legacy 읽기는 보존). 4) 신규 create가 input.copyWith를 whole mapper로 넣으므로 malicious/default submission의 isStarted/finished/score/frozen/recurring metadata를 create에 유입시키지 않는 명시 필드 정책 확인. 각 실제 검토와 답 transcript에 보존 부탁합니다. + +Agent: WIP 리뷰 4개 수용합니다. root는 신규 rootSegmentId 설치 한정 필드로 고정하며 최초 insert 때 series 기존 root를 상속, migration 때만 한 번 기존 first root를 골라 모든 segment에 backfill합니다. 이후 시간 역행/split UUID 순서로 재선택하지 않습니다. template/definition/default fallback 의존만 version trigger로 추적하고 실제 source mutation/ABA 검증을 추가합니다. 사용자 step 입력은 정확한 정수분(1–1440)만 허용하고 기존/backup decoder는 변경하지 않습니다. 신규 create에서 started/finished/score/frozen/recurring ownership 주입은 typed invalid로 거절합니다. 각 지적/답을 transcript에 보존 중입니다. + +## Delivery and form currentness review + +Root: 추가 WIP currentness 리뷰: ScheduleSaveWorkflow.retryDelivery는 effects.afterCommit await 뒤 repository.isCurrent(receipt)를 재검사하지 않으면 old-generation reconcile 완료를 현재 성공으로 표시할 수 있습니다. C01에서 다룬 것처럼 최종 generation 검증 및 route/current form owner 확인을 연결하고 deterministic pending reconcile→restore/reset→late completion 회귀 포함해주세요. 또한 같은 intent 동시 save가 DB중복은 안 만들어도 delivery 요청2회/UI이중navigation이 될 수 있으니 workflow/UI의 pending identity와 완료 receipt 처리 정책을 명확히 하세요. 통합 완료 시 재시도는 DB를 다시 호출하지 않는 actual widget/workflow 증거 필요합니다. + +Root: 최종 UI 검증에 포함 부탁합니다: 동일 Bloc에서 A 일정 save/review Future가 pending인 동안 B edit/create draft가 로드되거나 route가 바뀐 뒤 A 완료가 B state/success/navigation을 변경하지 않아야 합니다. 현재 WIP _submit은 isClosed/data generation만 확인하고 state 전체를 최신 state.copyWith로 바꾸므로 같은 세대의 다른 form owner도 구분해야 합니다. conflict reviewCurrent→dialog acceptance 또한 당시 form id/mutation identity와 baseline을 묶어야 합니다. 같은 pending save 중 입력/다른 event가 submissionStatus를 idle로 바꿔 중복 처리하는 경계도 actualBloc/widget 회귀로 확인하세요. 제품코드 반복수정 요청이 아니라 합의된 old-callback AC에 필요한 구체 시나리오입니다. + +## Committed watch, form replacement and localization review + +Root: SQL trigger 도입 시 Drift invalidation도 최종 검증해주세요. template/default/preparation writer가 trigger로 schedules/root version을 갱신해도 Drift는 원래 쓰기 테이블만 알 수 있습니다. 실제 watch가 의존 테이블을 포함하거나 commit 후 정확한 table invalidation을 받아야 하며 transaction rollback 때 성공 stream을 수동 발행하면 안 됩니다. 실제 aggregate/planning stream subscriber가 template/default/custom 변경 후 committed 최종값만 관찰하는 회귀가 필요합니다. 또한 recreate repository만은 앱/PID재시작 증거가 아니므로 durable receipt 검증은 최소 fileDB close/reopen/newgate로 분리 표기하세요. + +Root: 최종 diff에서 ARB 전체 formatting churn도 정리해주세요. 현재 app_en.arb 1227줄/app_ko.arb424줄 변경은 새 A08 문자열 양보다 크게 보입니다. 기존 배열/들여쓰기 형식 보존하고 필요한 key만 추가해 실제 변경이 리뷰 가능하도록 해주세요. tracked l10n 산출물은 새 ARB와 일치하게 재생성, sourcefreeze는 이 정리 뒤로 잡으면 됩니다. + +Root: snapshot test 리뷰: 'real preparation watch ... never rolled back value'는 rollback 직후 values/cache에 99가 없는지 확인 없이 다음 성공 save로 이어져, 실패 때 99가 잘못 발행되고 성공 때 중복발행이 없으면 values99==1 조건을 통과할 수 있습니다. 실패가 반환된 뒤 authoritative stream/cache가 여전히10임을 독립 assertion/정상 drain barrier로 확인하고 다음 commit이 실제 끝난 후99 발행을 구별해주세요. 고정sleep 대신 watch/DB barrier 사용. 동일 root버전/template 의존 stream은 최종 신규회귀에 포함 예정인지도 확인 부탁합니다. + +Root: 구체 신규폼 회귀 발견: ScheduleFormState.copyWith의 saveReceipt는 `saveReceipt ?? this.saveReceipt`이고 _emitLoadedDraft가 초기화하지 않아 A의 deliveryPending receipt가 B 새 draft에 남습니다. _submit은 그 receipt가 있으면 B 저장 대신 A 알림만 retry하므로 B가 저장되지 않았는데 success/navigation될 수 있습니다. nullable receipt/baseline/originalSchedule 등 폼교체용 초기화 경계를 명시하고, A partial→B create/edit→submit에서 B 실제 저장·old receipt 미재사용을 actual Bloc/workflow로 검증해주세요. 기존 originalSchedule도 copyWith(null)로지워지지 않는 경계이므로 새 draft는 fresh state 구성하는 방안이 더 명확할 수 있습니다. props에 saveFailure 중복도 보입니다. + +Agent: 해당 폼 교체 문제는 바로 전 수정에서 _emitLoadedDraft를 fresh ScheduleFormState 생성으로 변경했습니다. 이전 receipt/originalSchedule/recurrenceRule/validation을 새 초안에 유지하지 않습니다. A partial→B submit 실제 회귀를 추가하겠습니다. 현재 신규 aggregate+snapshot+backup+Bloc 확장 52건은 통과했고, 별도 역사 migration 5건도 통과했습니다. stream rollback 검증은 실패 후 별도 정상 dependency commit을 drain barrier로 삼아 99 발행이 없음을 다음 성공 전에 확인하도록 보강했습니다. root/template watch version 회귀도 추가 중입니다. + +## Q4: internal recurring duplicate policy + +Agent: 추가 구현 경계 질문 Q4: 제품 FormSubmission은 새 aggregate port만 사용하며 durable identity/receipt로 일반·반복 create/edit를 검증합니다. 기존 RecurringSchedulesUseCase.save는 이제 제품 callsite가 없지만, 내부 RecurringScheduleRepository.create는 기존 단위 테스트와 aggregate adapter가 직접 호출하며 `series exists => return`을 아직 갖고 있습니다. 추천은 이 내부 함수의 existing-series 조용한 성공을 typed conflict로 바꾸고, 중복 사용자 저장의 성공 재시도는 반드시 aggregate receipt 경계로만 처리하게 하는 것입니다. 기존 direct repository 반복-create 테스트는 그 명시적 내부 충돌과 aggregate 재시도 1commit을 각각 검증하도록 바꾸고, 미사용 RecurringSchedulesUseCase.save는 제거하여 우회 저장 entrypoint를 남기지 않겠습니다. 이 경계 정리를 채택할까요? + +Root: Q4 답변: 채택합니다. 내부 recurring create에서 series exists를 조용한 성공으로 처리하면 다른 입력/오래된 intent가 성공으로 오인되므로 typed conflict로 바꾸고, 사용자 재시도 성공은 aggregate의 동일 store/aggregate incarnation + intent/digest + durable receipt 검증으로만 허용하세요. 실제 전체 callsite 검색으로 미사용임을 확인한 RecurringSchedulesUseCase.save만 제거하고, 읽기/기타 사용 API는 보존하세요. 기존 direct repository 테스트는 명시적 충돌, aggregate 테스트는 동일 intent 1commit/no-op 및 다른 intent/input 충돌을 검증하세요. 이 Q4와 답변도 실제 원문 transcript에 포함하세요. 새 작성 화면 stale receipt 회귀와 앞서 요청한 watch rollback 중간 관측 검증도 최종 결과에 포함해 주세요. + +## Final dependency / draft / modal ownership review + +Root: 추가 root source검토: schedule_aggregate_schema의 place_version/template_steps/template/preparation_default는 schedule.aggregateVersion만 증가시키고 recurring root version은 갱신하지 않습니다. rootReceipt 재시도는 row version보다 먼저 root receipt로 성공하므로, 실제 recurring에 의존 가능한 writer라면 root에 마지막 receipt를 남긴 뒤 해당 발생의 place/template/default 변경→같은 following intent 재시도가 stale 성공일 수 있습니다. recurring definitions가 항상고정되어 template/default는 불가능한지 실제writer로 확인하고, 가능한 의존변경(place 등)은 root도 invalidate하거나 root receipt 검증에 의존version을 포함하세요. 단순 모든global변경 invalidate말고 실제의존대상만. 현재 rootReceipt의 lastMutationVersion이 동일하고 row만바뀐 경계 테스트 부탁드립니다. 불가능하면 실제소스근거로 transcript에 답 남기면 됩니다. + +Root: 추가검토: LoadScheduleFormDraftUseCase.create가 defaultPreparation을 먼저읽고 await 후 newBaseline을 읽습니다. 두읽기사이기본준비가변경되면 옛준비표시+새revision baseline이어서 createfence가이를잡지못하고 저장시새default사용/사용자가custom으로바꾼옛default복사 가능. A08 snapshot 계약이 edit뿐아니라새draft에도해당하는지 확인하여 production create의default와baseline을한snapshot으로읽도록port를묶거나 baseline먼저읽고뒤에preparation읽어중간변경시반드시conflict되게하세요(전자가명확). latch 회귀로default/baseline혼합없음을검증. optionaltestfallback을production계약검증대신쓰지않아야합니다. + +Root: UI await후ownership 마지막검토: schedule_multi_page_form의recurrence review/timeChoice는현재bloc.isClosed만, genericfailure retry는mounted만확인합니다. 같은Bloc에서A질문sheet열림→Bdraft전환→A응답이돌아오면B에confirmed/excludedSlots/timechoice/submit이적용될수있습니다. deliveryPending dialog도ModalRoute.isCurrent만으로same-route Bowner를구분못합니다. root앞선 formowner요청을UImodal까지일관되게적용해 originalstate의owner/token을capture하고모든await후동일owner일때만후속이벤트/routepop/snackbar를허용하세요. source변경없이형태만다른새draft를state.id만으로검증하지않도록. 제품widget회귀로최소늦은review/receipt응답이새draft를변경/닫지않는것을확인해주세요. + +Agent: 최신 검토 3건 모두 실제 경계로 확인했습니다. create default+baseline은 새 aggregate.newDraft transaction으로 묶었고, UI modal은 공개 opaque formOwner와 mutationId를 함께 캡처하여 모든 await 후 동일 owner+현재 route를 검증하도록 보강했습니다. place/template/default 의존 trigger의 root version 갱신을 추가 중입니다. 기존 신규 집중 52건+역사migration5건은 통과했으며, 이후 추가한 실제 UI ko/en 재시도 QA는 첫 화면의 비동기 validation→render 타이밍을 테스트 harness에서 조정 중입니다. 남은 것은 신규3경계 회귀, 실제 UI 캡처, focused 전체/analyze 정리 후 freeze 및 fullcoverage/정책검사입니다. Q4 채택 구현 완료, 원문 transcript도 최종 병합하겠습니다. + +Root: 역사fixture 마감시 test/fixtures/database에짧은provenanceREADME또는최종validation에서 schema_v2=baseline4529실행dump, schema_v1=ccc6272대조/추출방법·실제sourcecommitfullSHA·명령·fixturehash 명시해주세요. v1을실행생성한것인지 v2DDL에서과거diff에따라재구성한것인지정확히구분해야합니다. 역사실행binary/SQLCipher실증으로과장하지않고D04가실제암호화migration까지연결할수있게하면됩니다. + +Agent: 확장 경계 검증 45건 통과했습니다(/tmp/a08-final-boundaries2.log): 일반 첫/중간/마지막 단계 rollback, 반복 이번/이후 회차 준비·revision fault와 재시도, 이미 split된 마지막 segment 동일값 no-op, fileDB close/reopen+new gate 후 원 occurrence가 사라진 following receipt 복구, 실제 암호화 backup cutoff 경쟁까지 포함합니다. UI+Bloc 26건도 통과, ko/en 200% 텍스트 저장실패/DB재저장 없는 delivery retry 캡처 생성했고 2장 시각 확인했습니다. 신규 draft snapshot/root place 의존성 회귀 통과. 보완 중 발견한 occurrence-owned definition→root version 의존과 create baseline digest binding까지 추가하여 최종 analyzer 정리 중입니다. fixture README에는 v2 실제실행dump와 v1 역사diff 재구성을 구분하고 fullSHA/hash를 기록했습니다. 새 sqlite3 다운로드/upgrade 없이 기존잠금2.9.4를 dev dependency로 명시했습니다. + +Root: root가 /tmp/a08-ui의 KO/EN rollback/delivery-pending 4PNG 모두 직접 열어 확인했습니다. 새오류/partial본문·행동은200%에서접근가능합니다. EN dimmedform의Preparation 단계라벨줄바꿈/준비필드제목말줄임은기존U08전체레이아웃범위로구분하되A08전체form접근성이완료됐다고표현하지마세요. native실기기아닌syntheticwidgetcapture, actualDBfault/retry동작은같은테스트별도evidence로연결. finalsource변경후해당캡처영향없음/freezehash확인부탁드립니다. rootlegacy14issues read-only일치확인후누락hash만관리파일에추가중이며제품수정없음. + +Agent: A08 제품·테스트·fixture·ADR·pubspec 46개 경로를 /tmp/a08-source-freeze.json으로 고정했습니다(정확한 count는 manifest.paths로 확인 가능, sourcefreeze 생성 출력 대기). implementation base는4529d9f8, freeze 시HEAD는문서커밋반영fb874a4c입니다. ignored generator/l10n 재생성 후 final focused suite 및4PNG 재캡처 실행 중이며 다음순서 analyze→fullcoverage1회→필수gates입니다. 제품추가설계 결정 없음. scope cleanup은 변경 전 해당 schedule/series가 실제참조한 owned definition만 후보로 잡고 commit후참조없을때 삭제하여 무관orphan청소/semanticno-op부작용을 피했습니다. + +## Full suite regression review and corrections + +Root: root확인: 첫full /tmp/a08-full-coverage.log=1008pass/7fail, DAO/localsource5개는새metadata전체rowequality, startcommitwidget2개는L133 5초latchtimeout입니다. 원본실패로그별도보존후수정/refreeze필요. DAO비교를단순삭제하지말고사용자domain필드전체보존+identity발급/version증가를분리검증하세요. 주의ScheduleDao.insertReturning/writeReturning은SQL AFTER trigger값이반영되기전row를돌려줄수있어create반환null/update반환oldversion이실제DB값과다릅니다. product소비자가메타데이터를쓰는지확인하고필요하면sameTX내readback으로반환계약정리; legacy toCompanion(true)로실제persisted identity/version을재설정할위험도함께조사. startwidget은왜latch도달안하는지실제stream/read변경근거로고치고timeout늘리기만하지마세요. freeze47 manifest e7e9...는현단계구버전으로보존하고새manifest재동결시변경범위명시. + +Root: legacymetadata위험구체화: DB에서읽은Schedule에는non-null aggregateVersion/lastMutation*가있습니다. 그row를보관한뒤다른writer가version을올리고,옛row.copyWith(...).toCompanion(true)를updateSchedule/updateScheduleWithPlace에주면nonnull옛version/receipt를함께write해서AFTERtrigger의+1이현재/과거receiptversion과다시같아질수있습니다. SQL원문trigger외에도기존DAO수정경로에서concurrencymetadata는write권한없음으로빼거나최소동등한보호가필요합니다. 실제두번편집/oldrow재사용/ABA 뒤oldaggregateintent가성공no-op으로오인되지않는회귀로확인하세요. + +Agent: 동일 원인 확인했습니다. 기존 DAO의 whole-row legacy update에서 metadata는 명시적으로 Value.absent로 제외하고, create/update 반환은 같은 transaction 내 최종 readback으로 바꾸겠습니다. row equality 5건은 전체 domain 값 + 발급 identity/version의 독립 검증으로 전환하고 old persisted row 재사용/ABA 회귀를 추가합니다. start widget latch는 새 watch의 asyncMap→별도 DB transaction이 Flutter fake-async zone에서 열린 read transaction을 남긴 채 runAsync write를 대기시켜 생긴 것으로 확인 중입니다. schedule+place watch를 dependency 선언을 유지한 단일 SQL snapshot으로 바꾸면 추가 비동기 transaction 없이 기존 시작 경로를 유지할 수 있어 이를 집중 검증하겠습니다. 첫full실패/47path구freeze는별도보존 후수정3제품/테스트범위 refreeze합니다. count정정: 기존manifest정확히47개입니다. + +Root: startwidget timeout추가가설(검증필요): ScheduleRepository 생성직후 scheduleDao.watchScheduleList가 이제 asyncMap→db.transaction(getScheduleList)라, widgetfakeAsync zone에서pending watch transaction이생기면 tester.runAsync로await하는start transaction앞에queue되어 fake-zone continuation을pump하지않아막힐수있습니다. 첫tap/ObservedSession.entered 여부와실제queue를확인해초기watchsettled/readbarrier를harness에서실제펌프하거나제품streamqueue교착이면해결하세요. 단순5초증가/skip금지. 원래A07test의DBfault실패/재시도/route증거는유지해야합니다. + +Root: 최신DAO독립리뷰: watchScheduleList의변경전쿼리는 scheduleTime ASC orderBy가있었습니다. 현재단일SQL snapshot에는 ORDER BY가없어기존구독순서계약이유실됩니다(앞선asyncMap(getScheduleList)도같은누락). `ORDER BY s.schedule_time ASC`를보존하고같은시각tie는기존미정계약을새로추정할필요없습니다. 실제역순삽입→watch결과시간순 회귀가필요합니다. create/update sameTXreadback 및_withoutConcurrencyMetadata는방향맞고후보63테스트pass로그확인했습니다. 이정렬보완까지재freeze해주세요. + +Agent: 정렬 누락 수용했습니다. watch 단일 SQL에 ORDER BY s.schedule_time ASC와 역순 삽입 실제 watch 회귀를 추가했습니다. A07 테스트 파일은 전혀 수정하지 않았으며 원래 fault→retry→route 두 테스트가63건 집중 실행에서 통과해 asyncMap read-transaction 큐 원인을 뒷받침합니다. second full은 정렬 검토 도착 뒤 정확히 소유 PID1537에 SIGINT로 중단했고 로그를 /tmp/a08-full-coverage-before-ordering.log로 보존(통과 근거로 사용 안 함), v2freeze도보존했습니다. v3 최종freeze는49경로이며 v2대비DAO+DAO정렬test2경로만바뀝니다. 전체domain필드·발급identity·posttrigger version readback 비교와 legacyoldrow두경로 ABA 회귀 유지했습니다. + +## Materialization proof scope correction + +Root: root최종현재확인: full1018pass(55s),coverage88.96%(14113/15864),v3freeze49경로모두현재파일해시일치(40c22c1505b5ea38b1434904d26bd1c36a531996ac917234cc428f5528002beb),gitdiffcheck빈결과. handoff/negativeproof읽었습니다. validation JSON 완성후제품/Flutter소유권해제명시하고전달해주세요. 이후root가sourcecommit/blobhash검증·증거pack/#601전체문답·C01의A08진행기록·pushCI를합니다. 추가검증확장요청없습니다. + +Agent: 최종 JSON 작성 중입니다. 마지막 증거 매핑에서 기존 materialization 회귀의 count=3은 create가 이미 세 회차를 모두 materialize하여 실제 새 회차 추가가 아니라 재호출 no-op만 증명함을 발견했습니다. 제품/49path freeze는 그대로 유지하고, 별도 /tmp actualDB probe로 count65 최초60→materialize65 후 root version/revision/동일receipt 불변을 한 번 확인해 원래Q1 요구의 빈틈만 닫은 뒤 즉시 소유권 해제하겠습니다. 제품/회귀소스 추가확장이나 full재실행은 하지 않습니다. + +Root: 실제새회차materialization빈틈확인은필요합니다. 우선별도actualprobe로확인하되,그핵심assert(create60→추가5/rootversion·revision·sameintentreceipt불변)는기존count3 no-op회귀를대체하는영구테스트로남기는편이좋습니다. 제품소스변경없으면해당test변경만재동결+집중재실행하고, full1018은직전동일제품소스/이test보강전결과라고정확히기록하세요(전체재실행불필요;곧최종commit원격CI가전부실행). 원probe/추가회귀범위·최종manifestdelta를분명히하면됩니다. 이Q1필수검증빈틈외에새scope확장없음. diff --git a/plans/audit-2026-09-23/issues/C01.md b/plans/audit-2026-09-23/issues/C01.md index 1d02a0c2..98128b58 100644 --- a/plans/audit-2026-09-23/issues/C01.md +++ b/plans/audit-2026-09-23/issues/C01.md @@ -231,3 +231,8 @@ freshness/export/preview 실패 경로에 시작 generation 검사를 적용했 정확한 head `aa2fda68fbf3ac431738de3792f5d587d35b6dda`의 [Flutter run35949254397](https://github.com/DevKor-github/OnTime-front/actions/runs/35949254397)에서 959 tests/analyze와 coverage **88.72% (13504/15221)**를 통과했다. [Android run35949254443](https://github.com/DevKor-github/OnTime-front/actions/runs/35949254443)의 release APK·merged/packaged notification manifest 검사도 성공했다. artifact10787843483의 identity를 내려받아 source/test merge를 구분해 읽었다: 실제 synthetic merge `e74e6c52d6b1c036f29ac828307a35c9ec04302b`, APK SHA256 `a3d2858af8f52adea00b361da6511a8432e62bcdbedfb3bd2e045a86213ee38d`. 일회용 CI 서명이며 `device_delivery_verified: false`다. 근거 `c01-ci-validation.json`. 로컬 coverage88.74%와 원격88.72%는 각 실행의 실제 값이며 동일 값으로 합치지 않았다. 앞선 기록의 원격 CI 대기는 해소됐지만 A08/A09/D02 통합과 실제 모바일 검증은 남아 있어 #600은 OPEN이다. + + +## 2026-09-24 A08 통합 진행 갱신 + +A08 #601의 원자적 aggregate FormSubmission + ScheduleSaveWorkflow 연결을 `126161dab042365c89983934b1673d860c27d8d3`에 구현했다. 49파일 동결/commit blob 일치, 동일 최종 제품의 전체1,018 tests/88.96%, 최종 강화 회귀41/analyzer 및 KOEN200% 저장 결과 UI를 검증했다. a08-final-validation.json에 정확한 테스트 소스 차이를 기록했다. 위 과거 기록의 A08 구현 대기는 이 소스 기준 해소됐지만 A09 암호화 staging/runtime·writer fencing, D02 손상 복구, 실기기 및 최종 원격 CI는 여전히 별도 미완료다. C01 전체 완료·병합·출시로 표시하지 않고 OPEN 유지한다. diff --git a/plans/audit-2026-09-23/issues/U05.md b/plans/audit-2026-09-23/issues/U05.md new file mode 100644 index 00000000..c3b2df49 --- /dev/null +++ b/plans/audit-2026-09-23/issues/U05.md @@ -0,0 +1,197 @@ +# [P2][U05] 설정·준비·결과·백업·복구와 native 활성 경로의 한국어·영어 혼입을 없앤다 + +## 문제와 사용자 영향 + +OnTime은 KO/EN을 지원하지만 영어로 실행해도 알림 설정의 loading/off/error, 준비 중 남은 시간, 결과 응원 문구, 초기화 완료·복구 안내에 한국어가 남는다. 반대로 한국어 화면에 영어 고정 AM/PM과 native 버튼·권한 설명이 남을 수 있다. 같은 일정 시각을 24시간 숫자 뒤 AM/PM으로 중복 표현하거나, 첫 OS 선호 언어만 읽는 알림과 전체 선호 언어 목록을 해석하는 앱의 언어가 달라지는 경로도 있다. 단순 문자열 치환만으로는 이미 Bloc에 저장된 완성 문장, 화면을 연 상태의 locale 변경, DB가 열리지 않는 pre-DI 복구, OS에 예약된 문구를 해결하지 못한다. + +목표는 **동일한 데이터·작업·상태를 유지하면서 앱이 소유한 문구를 실제 해석된 KO/EN 언어와 시스템 시간 표시 선호에 맞게 보여 주는 것**이다. 사용자가 작성한 일정·장소·준비 이름은 번역하지 않는다. 로컬 전용 Android/iOS 앱이라는 제품 경계, 알림 개인정보 보호, 일정 시각/기간 계산과 저장·복구 동작은 바꾸지 않는다. + +- 우선순위: **P2 / 감사에서 확인된 결함**. 영어 사용자의 설정·복구·실패 안내 이해에 영향을 준다. 데이터 무결성·저장/알림 소유권 선행 P0/P1을 먼저 처리한다. +- 설계 상태: 전담 subagent `/root/grill_u05`가 `grill-with-docs`를 읽고 조사, root가 사용자 대신 순차 질문에 답한 **설계 완료 / 구현·실행 검증 전**이다. +- 고정 근거 SHA: **`fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f`**. 아래 GitHub source 링크는 모두 이 commit이다. 같은 workspace의 A08 제품 WIP와 혼동하지 않는다. +- A08 WIP에는 새 typed 저장 실패·receipt와 KO/EN 문구가 추가 중이다. 고정 commit의 ARB는 EN/KO 각각 **203개 메시지 키**이며 조사 시 WIP는 각각 212개였다. 이 숫자는 완성도 지표가 아니며 구현 착수 시 최종 source를 다시 inventory해야 한다. +- 이번 조사에서 Flutter·test·codegen·native 빌드를 실행하지 않았다. 소스 확인과 기존 테스트 목록 확인만 수행했다. 실제 KO/EN 렌더·기기 성공을 주장하지 않는다. + +## 현재 코드 근거와 누락 inventory의 시작점 + +| 영역 / 고정 source | 실제 확인 사항 / 필요한 조치 | +|---|---| +| [앱 root](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/app/screens/app.dart#L243-L248), [지원 언어](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/l10n/app_localizations.dart#L96-L99), [l10n 설정](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/l10n.yaml) | MaterialApp이 locale 강제 지정 없이 delegates/supportedLocales를 사용한다. 지원 순서는 EN, KO. 새 언어 선택 화면을 만들 필요가 없다. | +| [설정](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/my_page/my_page_screen.dart#L157), [알림 상태](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/my_page/my_page_screen.dart#L320-L425) | `분`, 알림에 일정 이름 표시, 확인 중, 상태를 불러올 수 없음, 꺼짐이 고정 한국어다. `_statusLabel`은 비동기 완료 시 현지화된 완성 문자열을 저장하므로 단순 key 치환만으로 locale 전환 시 재렌더가 해결되지 않는다. | +| [준비 상단](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/alarm/components/alarm_screen_top_section.dart#L46-L68), [시간 helper](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/shared/utils/time_format.dart) | 초/분/시간 단위와 나갈 때까지·초과 문장이 한국어다. `formatTime`, `formatElapsedTime`, 숫자 timer는 서로 다른 표시 계약이다. 기간 계산을 하나로 뭉개지 않고 각 기존 반올림·생략 정책을 보존한다. | +| [준비 단계 시간](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/alarm/components/preparation_step_tile.dart), [준비 설정 합계](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/my_page/preparation_spare_time_edit/preparation_spare_time_edit_screen.dart#L307) | 경과 시간 단위, localized prefix 뒤 한국어 분 suffix를 이어 붙이는 경로가 있다. 완성 문장·typed 수치로 표현한다. | +| [결과 문구 선택](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/early_late/bloc/early_late_screen_bloc.dart#L18-L42), [전체 응원 문구/이미지](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/shared/constants/early_late_text_images.dart), [체크리스트](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/early_late/components/check_list_box_widget.dart#L59) | 랜덤 선택 결과의 완성 한국어가 Bloc 상태에 들어간다. 안정적인 메시지 식별자/이미지 연결을 선택한 뒤 현재 locale에서 렌더한다. locale 전환을 이유로 재추첨하거나 결과 범주·체크 상태를 바꾸지 않는다. | +| [저장 실패](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/schedule_create/bloc/schedule_form_bloc.dart#L298-L308), [달력 삭제 실패](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/calendar/bloc/monthly_schedules_bloc.dart#L155-L172) | 한국어 fallback, `e.message`, `error.toString()` 노출 경로가 있다. A08 최종 typed 실패를 재사용하며 known failure→안전한 메시지/인자, unknown→generic localized error로 매핑한다. raw DB/파일/플랫폼 예외를 번역용 문구로 취급하지 않는다. | +| [반복 문구 helper](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/recurring/recurrence_labels.dart), [반복 설정](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/recurring/recurrence_settings_sheet.dart), [반복 검토](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/recurring/recurrence_review_sheet.dart) | `recurrenceText(ko,en)`를 여러 화면에서 사용하고 `day(s)`·`occurrences`와 수치 조합이 있다. 이미 양언어인 문구도 catalog에 포함하고 ARB로 이동할 때 중복 key/의미 변경을 피한다. DST 존재하지 않는/중복 시각·건너뛴 회차 이유도 포함한다. | +| [달력 시각](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/calendar/component/schedule_detail.dart#L213-L235), [주간 요일](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/home/components/week_calendar.dart#L364-L382) | `DateFormat.Hm(locale)` 뒤 `DateFormat('a','en')`로 24h 숫자와 영어 AM/PM을 동시에 표시한다. 요일도 월~일 고정이다. 이미 선택된 civil time을 사용해 현지화한다. | +| [기존 기간 formatter](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/shared/utils/duration_format.dart), [EN 단복수](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/l10n/app_en.arb#L343-L361) | hour/minute ARB plural이 이미 있다. U04는 결과의 정확히 1/2시간 버그와 이 helper 연결을 선행 처리한다. U05가 같은 formatter를 새로 만들거나 U04 수치 정책을 바꾸지 않는다. | +| [백업/복원/초기화 UI](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/my_page/my_data_screen.dart#L409-L454), [복구 화면](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/startup/screens/local_data_recovery_screen.dart) | C01/D03에서 현지화한 신규 receipt/status와 아직 한국어인 완료·재시작·삭제 확인이 공존한다. 미확인 정리/부분 완료를 번역 과정에서 성공으로 축약하면 안 된다. | +| [pre-DI startup root](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/startup/screens/local_startup_gate.dart#L71-L75) | 복구용 MaterialApp에도 delegates/supportedLocales가 이미 있다. 이 경로에서 DI/DB 재접속 없이 같은 bundled localization을 쓸 수 있는지 검증한다. | +| [알림 locale 선택](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/core/services/notification_service.dart#L90-L92), [reconcile locale provider](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/domain/use-cases/reconcile_alarms_use_case.dart#L71-L72) | `PlatformDispatcher.instance.locale.languageCode`는 첫 선호 언어만 읽는다. 앱의 전체 preferred locales 해석과 달라질 수 있다. resolved language를 platform/presentation adapter에서 전달하며 domain에 BuildContext를 넣지 않는다. | +| [기존 알림 내용 digest](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/domain/entities/scheduled_notification_content.dart#L25-L60), [기존 재사용 판정](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/domain/use-cases/reconcile_alarms_use_case.dart#L777) | normalized language, title/body, detailed, zone이 SHA256에 포함되고 기존 판정이 contentDigest를 비교한다. A05 경로를 재사용하면 언어 변경을 새 알림 엔진 없이 감지할 수 있다. digest는 익명화·암호화 보장이 아니다. | +| [iOS AlarmKit 설명](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/ios/Runner/Info.plist#L42-L43), [native 버튼](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/ios/Runner/AppDelegate.swift#L218-L238) | 앱 소유 usage description, Stop/Open이 영어 고정이다. 앱이 소유한 native bundle 리소스를 사용하고 실제 지원 build/device에서 확인한다. OS 시스템 경고 본문 전체를 OnTime이 번역하는 것은 아니다. | +| [Android native 정책](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/android/app/src/main/kotlin/club/devkor/ontime/NativeAlarmPolicy.kt), [과거 full-screen 화면](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/android/app/src/main/kotlin/club/devkor/ontime/AlarmRingingActivity.kt) | 파일에 영어 Start preparing/Dismiss/Alarm stopped가 존재한다는 사실과 현재 release 활성 경로는 다르다. 현재 policy의 비활성 full-screen 경로는 reachability 근거를 남긴다. 번역을 위해 활성화하거나 Android Schedule Notification을 Alarm으로 재정의하지 않는다. | +| [현재 bundled 정책](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/lib/presentation/my_page/privacy_policy_screen.dart) | 한국어 본문과 시행일·연락처가 있다. O02가 법적 사실과 KO/EN 본문 승인·공개 URL을 책임진다. U05는 이를 임의 번역해 승인 완료로 표시하지 않는다. | + +이 표는 확정 시작점이며 exhaustive 파일 목록이라고 주장하지 않는다. 구현 시 앱 root, onboarding/home/calendar, 일정 단독·반복/시간·장소/준비 양식, 실행/결과, 설정/백업/복구, 모달·toast·Semantics·tooltip, 활성 native 리소스와 오류 adapter를 끝까지 inventory한다. 각 항목은 `source path + 상태 + 소유자 + 현재 입력 종류 + 새 key/typed 인자 + KO/EN 검증 + 제외 사유`로 추적한다. 주석·로그·사용자 데이터·허용된 브랜드/기술 식별자·O02 pending·미활성 native 코드는 서로 구분한다. + +## 확정 계약 + +### 1. catalog와 도메인 언어 + +- `lib/l10n/app_en.arb`가 template인 기존 구조를 사용한다. KO/EN 완성 문장, typed placeholder, 필요한 plural을 둔다. 조각을 조합해 한국어 조사·영어 어순·단복수가 틀어지지 않게 한다. 의미가 다른 메시지를 키 수를 줄이려 억지 통합하지 않는다. +- 앱 소유 사용자 문구의 Dart inline KO/EN 분기와 하드코딩을 ARB 중심으로 정리한다. native는 해당 플랫폼의 실제 bundle localization 형식을 사용한다. Dart ARB를 native UI가 직접 읽도록 억지로 연결하지 않는다. 앱/알림의 의미·언어 해석·번역 목록은 일치시킨다. +- domain/Bloc의 상태는 stable failure/status/message 식별자와 타입 있는 인자를 보존한다. UI 또는 적절한 platform adapter가 현재 resolved language에서 렌더한다. BuildContext, 번역용 DB 읽기, 원격 번역 호출을 domain에 넣지 않는다. +- 응원 문구의 구간·무작위 선택·이미지 연결은 기존 제품 의미를 유지하고 선택된 ID를 한 번 보관한다. locale 변경/일반 rebuild로 다시 선택하지 않는다. 모든 ID와 fallback에 KO/EN 문구가 존재해야 한다. +- `CONTEXT.md`의 Schedule, Preparation Step, Preparation Duration, Schedule Spare Time, Local Profile, Schedule Notification, iOS AlarmKit Alarm을 따라 문구를 구별한다. 로그인/서버 계정이 없는 앱에 account 복구를 새로 암시하지 않는다. 준비/이동/여유 시간을 번역 과정에서 합치지 않는다. +- [ADR 0009](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/docs/adr/0009-use-alarm-language-only-for-ios-alarmkit.md), [로컬 전용 ADR 0011](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/docs/adr/0011-make-ontime-local-only.md), [시간대 ADR 0021](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/docs/adr/0021-anchor-schedules-to-a-named-time-zone.md), [private 알림 ADR 0030](https://github.com/DevKor-github/OnTime-front/blob/fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f/docs/adr/0030-default-to-private-notification-content.md)을 따른다. 새 도메인 용어·되돌리기 어려운 아키텍처 결정이 없으므로 이번 설계에서 CONTEXT/ADR을 추가하지 않는다. + +### 2. locale 해석과 작업 중 변경 + +현재 지원 순서 `[en, ko]`와 MaterialApp 기본 해석을 보존한다. 설치된 Flutter source `/Users/ejunpark/Library/flutter/packages/flutter/lib/src/widgets/app.dart`의 `basicLocaleListResolution`(146행 이후)을 읽어 아래 결과를 도출했다. **코드 알고리즘으로 확인한 예상 결과이며 이번 조사에서 실행한 Flutter 테스트 결과는 아니다.** 구현 검증은 실제 delegates/MaterialApp과 서비스 adapter를 함께 사용한다. + +| OS preferred locales | 현재 정책에 따른 effective locale | 회귀 목적 | +|---|---|---| +| `[fr, ko]` | `ko` | 첫 unsupported 언어만 보고 알림을 EN으로 만드는 분기 제거 | +| `[fr, en]` | `en` | 두 번째 지원 언어 선택 | +| `[fr]` 또는 모두 미지원 | 첫 지원 locale `en` | unsupported fallback을 별도 임의 언어로 바꾸지 않음 | +| `[]` / 초기 preferred 목록 없음 | 첫 지원 locale `en` | pre-DI/cold-start 안전 fallback | +| `[ko_KR]` | 지원 `ko` | language match | +| `[en_US]` | 지원 `en` | language match | + +locale 변경은 draft, validation 원인, 실행 시간/체크 상태, save/restore/reset operation, receipt identity, 선택된 응원 ID/이미지를 초기화하지 않는다. 번역 문자열을 새로 렌더하되 DB content revision·backup freshness를 dirty로 만들지 않는다. locale 변경 중 늦게 도착한 비동기 결과도 원래 작업의 소유권/현재성에 따라 처리한다. 번역을 얻으려고 DB를 다시 열거나 reset을 다시 실행하지 않는다. + +### 3. 시각과 기간 + +- 날짜/요일/표시 숫자·단복수는 실제 resolved KO/EN을 사용한다. 하루의 시각은 기존 Schedule Time Zone에서 변환된 civil time을 `MaterialLocalizations`와 `MediaQuery.alwaysUse24HourFormat`으로 표시한다. 24시간 요청을 존중하고 그 외에는 해당 locale 기본 형식을 따른다. 24h 숫자 뒤 AM/PM을 별도 붙이지 않는다. +- 이미 계산된 instant/time-zone/DST ambiguity 선택을 바꾸거나 locale에 따라 다른 날짜·시각을 저장하지 않는다. 표시는 civil time이고 정렬·실행 판단은 기존 데이터 계약을 그대로 따른다. +- 경과/남은 **기간**에는 12/24h 형식을 적용하지 않는다. `00 : 00`/시간 포함 timer는 기존 표시를 유지하되 접근성 읽기 단위는 KO/EN으로 제공한다. 1시간을 하루의 1시로 바꾸거나 24시간 이상을 wrap하지 않는다. +- U04 [#625](https://github.com/DevKor-github/OnTime-front/issues/625)의 결과 기간 `abs + 초 버림 + 0~59초=0분`, 정확한 1/2시간 수정 및 기존 localized helper 재사용을 보존한다. 준비 `formatTime`/`formatElapsedTime`의 현재 반올림·초 생략 정책과 결과 메시지 선택의 ceil은 서로 다르므로 일괄 통일하지 않는다. + +### 4. 오류·복구·백업 + +Known typed 실패를 그 의미에 맞는 KO/EN 문장으로 렌더한다. 재시도 가능한 저장 실패, stale preview, DB 반영 전 실패, 반영 후 알림 정리 미확인, reset pending/일부 삭제/완료를 구별한다. unknown exception은 안전한 일반 메시지로 표시하고 SQL·파일 경로·native localizedDescription·비밀번호·사용자 입력 전체를 노출하지 않는다. 로그 정책도 완화하지 않는다. + +pre-DI 복구는 기존 복구 MaterialApp의 bundled localization을 사용한다. DI/DB가 불가능한 상황에서 resource를 렌더하려 DI/DB를 다시 열면 안 된다. locale 변경 중에도 “데이터가 보존됨/이미 삭제됨/정리가 미확인됨”이라는 실제 receipt 의미가 바뀌지 않는다. OS 파일 picker 취소는 오류/성공으로 잘못 바꾸지 않으며 OS가 표시하는 provider 문구는 OS 언어 관찰로 별도 기록한다. + +### 5. 알림·native 경계 + +- 앱과 동일한 지원 언어/선호 목록 해석을 platform adapter에서 재사용한다. active, cold start, 복구 경로와 알림 생성에서 서로 다른 첫-locale shortcut을 쓰지 않는다. 현재 domain language provider의 플랫폼 접근은 최소 adapter 주입으로 정리하되 광범위 아키텍처 변경을 하지 않는다. +- A05의 `ScheduledNotificationContent` normalized language + content digest와 기존 desired/existing 비교, A15 owner를 사용한다. 다음 정상 reconcile에서 현재 pending 알림의 옛 언어 차이를 감지하여 안전한 대상만 갱신한다. 시각/ID가 같다고 옛 언어를 영구 재사용하지 않는다. 새 locale watcher/retry/reconciliation 엔진을 만들지 않는다. +- 이미 표시된 OS 알림의 언어가 즉시 바뀐다고 보장하지 않는다. platform별 한계와 다음 생성/기존 reconcile 반영 시점을 명시한다. provider 완료가 locale 변경보다 늦게 돌아오면 old completion이 최신 locale의 delivery를 확인한 것처럼 표시되지 않도록 A15의 최신 요청/후속 reconcile 회귀를 재사용한다. +- 상세 알림 OFF에서 일정/장소/준비 본문을 노출하지 않는 A11 경계, 최소 routing payload/A12, A15 ownership을 그대로 유지한다. 번역용으로 raw payload를 추가하거나 digest 대신 원문을 fingerprint/로그에 기록하지 않는다. +- 앱이 소유한 iOS usage description/AlarmKit 버튼/접근성, Android 활성 notification 채널·버튼/접근성 문구를 번들에 포함한다. iOS AlarmKit은 실제 build/device capability를 전제로 하고 fallback은 notification 용어다. 미활성 Android full-screen 경로는 현재 reachability와 제외 근거를 기록한다. 문구 수정을 위해 권한·full-screen 정책을 활성화하지 않는다. +- OS permission dialog의 시스템 부분, OS settings, OS 파일 provider를 앱이 강제 번역하지 않는다. native 앱 소유 부분과 OS 소유 부분을 실제 기기에서 구별해 관찰한다. + +## 의존성과 비범위 + +| 연결 | 계약 | +|---|---| +| A08 [#601](https://github.com/DevKor-github/OnTime-front/issues/601), C01 [#600](https://github.com/DevKor-github/OnTime-front/issues/600), D02 [#604](https://github.com/DevKor-github/OnTime-front/issues/604), D03 [#595](https://github.com/DevKor-github/OnTime-front/issues/595) | 최종 typed save/restore/reset 상태와 receipt를 번역한다. 번역 이슈에서 저장 원자성·reset 엔진·migration/backup 형식을 변경하지 않는다. A08 WIP가 안정된 뒤 최종 문구 inventory를 갱신한다. | +| A05 [#588](https://github.com/DevKor-github/OnTime-front/issues/588), A11 [#589](https://github.com/DevKor-github/OnTime-front/issues/589), A12 [#591](https://github.com/DevKor-github/OnTime-front/issues/591), A15 [#594](https://github.com/DevKor-github/OnTime-front/issues/594) | 기존 content digest/privacy/routing/owner 회귀를 재사용해 locale 변경과 늦은 completion을 확인한다. | +| U04 [#625](https://github.com/DevKor-github/OnTime-front/issues/625) | 결과 기간만 선행 현지화. 전체 응원 문구·설정/오류·12/24h는 U05. U04 통과가 U05 완료가 아니다. | +| O02 [#622](https://github.com/DevKor-github/OnTime-front/issues/622) | 정책 본문 법적 사실·양언어 승인·시행일/연락처·public URL은 O02 소유. U05는 승인된 정책 리소스 연결과 주변 UI를 검증한다. 미승인 정책을 “번역 누락 0”에 숨기지 않고 pending dependency로 남긴다. | +| U07 / U08 [#597](https://github.com/DevKor-github/OnTime-front/issues/597) / U09 | 각각 접근성 대체 조작 / 전체 작은 화면·큰 글자 레이아웃 / 오류의 행동 계약을 소유한다. U05는 번역 문구의 semantics와 직접 발생한 overflow를 수정하고 기존 광범위 결함은 교차 기록한다. | +| T01 [#615](https://github.com/DevKor-github/OnTime-front/issues/615), T02 [#616](https://github.com/DevKor-github/OnTime-front/issues/616) | 전체 mobile E2E·platform RC 증거를 공유한다. host widget과 native 실제 OS 문구/기기 검증은 구별한다. | +| R02 [#617](https://github.com/DevKor-github/OnTime-front/issues/617), O01 [#621](https://github.com/DevKor-github/OnTime-front/issues/621) | 재현 가능한 generation/check 명령과 문구 관리 안내를 연결한다. ignored 생성 Dart와 tracked l10n Dart를 혼동하지 않는다. | + +새 언어, 앱 내 language 설정, 네트워크 번역/외부 번역 SDK·서비스, 새 package, DB schema/migration, 사용자 데이터 번역, 반복/점수/준비 시간 정책, 알림 권한·배송 정책, 정책 법률 내용의 독자 결정, 전체 디자인 개편은 비범위다. 무관한 CONTEXT 중복·문구 외 구조 문제를 동시에 정리하지 않는다. + +## Negative / 경계 검증 matrix + +아래는 구현 시 수행할 AC이며 현재 통과 결과가 아니다. 실제 locale delegate와 앱 소비 경로를 사용하며 영어 key 존재/한글 regex 없음만으로 성공 판정하지 않는다. + +| ID | 입력·실패 / 관찰 경로 | 기대 결과 | +|---|---|---| +| L01 | 실제 EN 설정 loading→off→permission/cleanup status→실패 | 앱 소유 문구가 영어, 상태 의미와 스위치 동작 유지. 고정 한국어가 있던 기준 source에서 negative 재현. | +| L02 | 같은 KO 경로와 앱 정상 화면 | 올바른 한국어, 알림/알람 capability 구별, regression 없음. | +| L03 | KO→EN→KO, 설정 비동기 fetch 완료 전/후 | 현재 locale로 렌더. stale 완성 문자열 고착·중복 side effect 없음. | +| L04 | `[fr,ko]`, `[fr,en]`, 미지원만, empty, ko_KR/en_US | 실제 앱과 알림/cold/pre-DI adapter의 effective locale가 위 표와 일치. 첫 선호 언어 shortcut negative 탐지. | +| L05 | 양식 validation/반복 search limit·DST gap/fold·stale save와 unknown exception | typed 의미의 안전한 KO/EN 문장. raw SQL/native message/path/비밀번호 미노출. 인자 형식·위치 정확. | +| L06 | 저장·복원 진행 중 locale 변경 및 old completion | draft/operation/receipt identity 보존, 중복 저장·복원·reconcile 발생 없음, 최신 화면의 상태에 오래된 문구를 덮어쓰지 않음. | +| L07 | 모든 랜덤 응원 ID·fallback, 범주 경계 및 locale 전환 | 모든 ID KO/EN 존재, 선택/이미지·late/early 결과·체크 상태 유지. 실제 selector를 주입 가능한 deterministic 입력으로 검증하고 별도 가짜 선택 알고리즘을 만들지 않음. | +| L08 | 기간 0/1/59/60/61/3599/3600/3601/3660/7200/86400 이상, 해당 경로의 양/음수 | 기존 분기·절댓값·초 버림/ceil/생략 정책 보존, 정확한 locale 단위/단복수. U04 경계 기존 테스트 재사용. | +| L09 | 00:00/12:00/13:05/23:59, KO/EN, 24h flag on/off | 실제 Material 표준 시각과 일치, 24h 뒤 AM/PM 중복 없음, locale 기본형 존중. | +| L10 | device zone≠schedule zone, DST gap/fold에서 기존 선택된 civil time | 표시만 바뀌고 UTC instant·날짜·zone·정렬·선택 회차 동일. 데이터 revision 불변. | +| L11 | 경과 timer 00:00·1시간·24시간 이상 + semantics | timer에 AM/PM/날짜 wrap 없음. 숫자 화면과 현지화한 접근성 기간 의미 일치. | +| L12 | 반복 interval/count 1,2,다수·종료 없음/날짜/횟수·skipped 이유 | `day(s)` 임시 표기 대신 올바른 plural/문장, 사용자 입력 제목 보존, 계산된 회차/수치 동일. | +| L13 | 백업 password 검증·picker 취소·freshness 실패·stale preview·commit 전후 실패 | KO/EN 원인·조치 구별, 취소를 실패/성공으로 바꾸지 않음, partial receipt를 완료로 오역하지 않음. | +| L14 | DB/DI 실패 상태에서 pre-DI 복구, reset pending/일부 삭제/완료·재시작 | KO/EN bundled resource 렌더, 번역 때문에 DI/DB 재접속·삭제를 재실행하지 않음. 외부 백업 삭제 아님을 유지. | +| L15 | 같은 pending ID/시각, 생성 후 KO→EN, 다음 정상 reconcile | 기존 content digest 차이로 안전한 현재 pending 대상 갱신. 권한/시간/사용자 데이터 content revision 변경 없음. | +| L16 | provider 요청 중 locale 변경·늦은 old completion | A15 owner/후속 reconcile이 최신 요청을 처리. 이전 완료를 새 언어 성공으로 잘못 확정하지 않음. | +| L17 | private OFF/ON · schedule title에 KO/EN/이모지·민감 문자열 | OFF raw title/place/preparation 노출 없음. ON 허용 title은 사용자 원문 그대로, 최소 payload·로그 정책 유지. | +| L18 | 이미 OS에 표시된 옛 언어 알림 | 즉시 변경을 거짓 보장하지 않고 플랫폼 한계 기록. 다음 생성/정상 pending 갱신 언어만 실제 확인. | +| L19 | 실제 iOS 지원 AlarmKit / fallback + Android 활성 notification | app-owned 버튼/description/channel/semantics의 KO/EN 리소스가 실제 artifact에 포함되고 올바른 경로에서 사용됨. 미활성 full-screen을 활성화하지 않음. | +| L20 | OS picker/settings/permission system 소유 문자열 | 앱 소유 번역과 OS locale를 구분해 기록. 앱 번역 테스트가 OS 문자열을 fabricated fixture로 대체하여 통과시키지 않음. | +| L21 | 작은 화면/200% KOEN, 긴 수치·사용자 입력·오류 | 주요 문구 의미와 CTA/semantics 접근 가능, 직접 도입한 overflow 수정. 전체 U07/U08 접근성·레이아웃 완료로 확대하지 않음. | +| L22 | ARB key 누락/placeholder 타입 불일치/plural fallback 누락·stale generated output | generation/정합성 검증 실패. key 개수만 같거나 EN key에 KO 값을 복제한 결과를 합격시키지 않음. | +| L23 | O02 정책 번역 미승인 / 사용자가 한국어 제목 입력 | 각각 dependency pending / 번역하지 않는 원문으로 명시. 두 경우를 한글 누락 자동 FAIL 또는 전체 번역 완료 PASS로 오분류하지 않음. | +| L24 | offline cold start와 language 변경 뒤 process relaunch | 필요한 문구가 artifact에 포함, 외부 다운로드/번역 네트워크 없음. 정상·복구 root의 effective locale 일치. | + +## 구현 순서와 검증 산출물 + +1. 구현 착수의 정확한 SHA·선행 PR 상태를 읽고 A08/C01/D03/U04 최종 소스 기준 inventory를 갱신한다. 현재표와 차이를 남긴다. 각 제외 항목은 reachability/OS 소유/사용자 입력/정책 승인 대기 근거가 있어야 한다. +2. 실패·상태·응원 문구 stable ID/typed 인자와 presentation 렌더를 연결한다. 기존 domain 타입을 재사용하고 locale용 상태 엔진을 추가하지 않는다. ARB complete sentence/placeholder/plural, active native 리소스를 추가한다. +3. 실제 효과 locale 해석을 앱과 알림 adapter가 공유하게 하고 기존 digest/owner로 다음 정상 reconcile 언어 갱신을 검증한다. 날짜/요일/시각 formatter 소비 지점을 정리하되 원래 데이터 변환과 기간 계산은 유지한다. +4. 실제 앱 위젯에서 negative→positive 증거를 남긴다. 예: 영어 설정에서 한국어 off 상태, 영어 준비 상단, 한국어만 있는 결과/복구 화면, 24h+AM/PM 중복, 첫 미지원+둘째 KO에서 알림 EN 불일치. 이미 올바른 ARB helper 단위 테스트만 실행해 소비자 연결 누락을 숨기지 않는다. +5. key/placeholder/plural 및 실제 locale 전환·typed 오류·알림 privacy 회귀를 수행한다. 의미 없는 구현 mirror test·전체 복제 fixture 대신 기존 workflow/receipt/fault tests에 locale assertion을 연결한다. matrix별 명령·결과·미실행 사유를 남긴다. +6. `flutter gen-l10n` 등 저장소에서 사용하는 정식 generation과 필요한 build_runner를 실행하고 `flutter analyze`, 관련 unit/widget/native 검사 및 변경 영향에 맞는 전체 suite를 수행한다. tracked `lib/l10n/app_localizations*.dart`는 ARB와 함께 반영하고 ignored `*.g.dart`, `*.config.dart`, `*.freezed.dart`, `*.mocks.dart` 등은 commit하지 않는다. 분석과 test 성공만으로 native 동작을 증명하지 않는다. +7. KO/EN 작은 화면·200%/Semantics 증거와 실제 Android/iOS 활성 native 리소스/권한 문구 관찰을 source SHA·artifact hash·locale·OS/device 조건에 묶는다. 기존 T01/T02 RC 검증을 재사용하며 미실행 native 조건은 issue OPEN에 남긴다. 번역 승인이 안 된 정책은 O02 pending으로 정확히 남긴다. + +## Acceptance criteria + +- [ ] 고정 근거→구현 최종 SHA의 앱 소유 문구 inventory가 모든 실제 사용자 경로/상태와 제외 근거를 추적한다. grep/key count 단독 완료 판정이 없다. +- [ ] KO/EN complete sentence·typed placeholders·plural과 native 활성 리소스가 정합하며, tracked generated l10n이 정식 재생성 결과와 일치한다. +- [ ] 설정·양식/반복·준비·결과·백업/초기화·pre-DI 복구의 실제 상태 matrix에서 KO/EN 완성 문장과 조치 의미가 올바르다. unknown exception은 안전하게 매핑한다. +- [ ] locale 변경 중 draft/진행 작업/receipt/응원 선택·이미지/체크 상태·DB revision이 보존된다. pre-DI 번역 때문에 DI/DB 재접속하지 않는다. +- [ ] 지원 순서/선호 목록 해석과 fallback이 앱/알림/cold/pre-DI에서 일치한다. `[fr,ko]`처럼 첫 미지원 언어인 실제 회귀가 검증된다. +- [ ] 시각은 시스템 24h 요청/locale 기본을 따르고 AM/PM 중복이 없다. 기간 수치 정책·U04 결과·time-zone/instant/DST/정렬이 보존된다. +- [ ] 기존 digest/A15 owner로 pending locale 차이와 늦은 provider 완료를 안전하게 처리한다. private 알림/최소 payload/로그·권한 정책을 보존한다. +- [ ] 직접 수정한 UI의 KO/EN small/200%/Semantics 증거가 있고 직접 생긴 overflow가 없다. 전면 U07/U08/U09 완료와 구분한다. +- [ ] 활성 native KO/EN app-owned 리소스가 실제 artifact와 지원 기기 경로에서 확인된다. host widget만으로 통과 처리하지 않으며 미실행 플랫폼 조건을 남긴 채 닫지 않는다. +- [ ] O02 승인 정책 연결이 검증되거나 명시적인 미완료 의존성으로 남는다. “전체 번역 완료”가 정책 미승인·native 미실행을 숨기지 않는다. +- [ ] 새 네트워크/번역 SDK/package/DB migration/언어설정 기능 없이 기존 로컬 전용 동작과 회귀 suite가 유지된다. 모든 실제 질문·답변·보충을 아래에 보존한다. + +## grill-with-docs 실제 질문·답변 전체 + +질문은 전담 `/root/grill_u05`가 한 번에 하나씩 보내고 root 응답 후 다음 질문을 진행했다. 아래는 실제 전송된 원문이며 root는 사용자가 위임한 답변자다. 조사 중 사용한 소스는 위 고정 SHA와 별도 명시한 설치 Flutter source이고 A08 WIP 수치를 baseline으로 치환하지 않았다. + +### Q1 — 문구 범위·상태·정책/native 소유권 + +**Agent 원문** + +> U05 Q1 — 고정 HEAD fb874a4c5d174ba9ae4df6e22ceb6bf92e6da49f를 근거로 조사했습니다(A08 WIP 별도). 현재 ARB 외에 recurrenceText(ko,en), Bloc에 저장된 완성 한국어 상태/무작위 응원 문구, pre-DI 복구 화면 한국어가 있고 native iOS AlarmKit 설명은 영어 고정입니다. 권고: 지원 언어 KO/EN과 현재 시스템 locale 선택을 유지하며 신규 언어/언어설정은 만들지 않고, 앱 소유 사용자 문구를 ARB의 완성 문장·typed placeholder·plural로 모읍니다. Bloc/domain은 오류·상태·응원 문구 ID/인자만 보관하고 현재 locale에서 렌더해 화면이 열린 채 locale 변경에도 같은 상태·선택된 응원 이미지가 유지되게 합니다. 사용자 입력(일정/장소/준비 이름)은 번역하지 않습니다. O02의 정책 본문·법적 사실/승인·공개 URL은 O02 소유이고 U05는 승인된 KOEN 콘텐츠 연결/주변 UI만 검증합니다. native는 실제 앱 소유 활성 경로 문구/권한 설명·접근성까지 포함하되 OS가 소유한 picker/settings 문구를 앱이 강제로 번역하지 않고, Android를 Alarm이라고 새로 부르거나 권한·delivery 동작을 바꾸지 않습니다. 이 범위/소유권으로 확정할까요? + +**Root 원문** + +> Q1 채택합니다. 지원KOEN/시스템locale그대로, 언어추가/앱내언어설정신설없음. 앱소유문구는ARB완성문장/typedplaceholder/plural로정리하고Bloc/domain에localized완성문자열대신안정ID/인자보관, 렌더현재locale. locale변경시랜덤응원선택/이미지/도메인결과/초안·진행중작업을재추첨·초기화하지않습니다. 사용자입력번역금지. pre-DIRecovery는실제DI/AppLocalizations사용가능시점을보며동일KOENresource를안전하게쓸수있는최소연결; 부트실패때번역얻으려DI/DB재열어실패반복금지. O02법적정책사실/본문승인/publicURL소유권유지, U05법률내용독자번역/발명금지. native활성app-owned 권한설명/알림·접근성문구포함,OS문구강제번역없음,Androidnotification과iOSAlarmKit의실제capability용어정확. A11privacy/상세OFF본문원문금지, nativepayload최소정보/A12routing/동작권한불변. 현재고정HEAD와A08추가KOEN새문구는구현착수시최종소스로재inventory하고누락없이범위를닫으세요. + +### Q2 — 시각·기간·예약 알림의 locale 변경 + +**Agent 원문** + +> U05 Q2 — 시간 표시에 현재 TimeOfDay.format(context), DateFormat.add_jm(), HH:mm 뒤 영어 AM/PM, 한국어 고정 요일/초·분 단위가 섞입니다. 권고: 날짜·요일·숫자/단복수는 실제 해석된 KO/EN locale, 하루의 시각은 MediaQuery.alwaysUse24HourFormat과 MaterialLocalizations의 표준 포맷을 따라 시스템 24h 요청을 존중(그 외 locale 기본), 24h에 AM/PM 중복 금지. 기존 Schedule Time Zone/UTC instant/DST 선택·정렬·기간의 절댓값/초버림/올림·기록은 바꾸지 않고 표시용 civil time만 기존 변환 결과를 사용합니다. timer의 00 : 00 형태는 경과 기간이지 시각이므로 12/24h를 적용하지 않되 읽어 주는 단위는 현지화합니다. 알림 문구는 실제 앱 effective locale와 일치하게 active/cold-start 생성 경로를 검증하고, 이미 OS에 예약·표시된 문구는 locale가 바뀌었다고 과거 DB/알림을 전부 재작성·재예약하지 않고 다음 기존 reconcile 시 신규/갱신 대상에 반영하는 경계를 문서화합니다. 이 정책으로 확정할까요? + +**Root 원문** + +> Q2 대부분채택합니다. 날짜/요일/수치KOEN,하루시각은기존zone변환된civil값에MediaQuery.alwaysUse24HourFormat+MaterialLocalizations표준포맷,24h AM/PM중복없음. 절대instant/DST/정렬·기간정책/DB기록은불변; timerelapsed는12/24h적용없고semantic단위현지화. 알림은생성시실제effective locale일치/active+coldstart검증. locale변경만으로과거DB/이미표시알림전체재작성또는새재예약엔진금지에는동의하나, 다음정상기존reconcile에서현재pending문구가이전locale인경우갱신필요성을감지할수있어야합니다. 같은시각/ID라oldlocale가영구재사용되면안됨: 기존A05renderedcontent/locale차이판정과A15owner를재사용하여안전한pending대상만갱신, raw본문을fingerprint에노출금지. 이미표시된OS알림은플랫폼별변경보장없고다음생성부터정상표시라는한계별도. 현재구현의감지가능성/필요최소localeidentity를조사하여정확히정리해주세요. locale변경은콘텐츠revision/backupdirty/알림권한정책변경아님. + +**Agent 조사 보충 원문** + +> U05 Q2 조사 보충 — 현재 ScheduledNotificationContent는 normalized languageCode/title/body/private 여부/zone을 SHA256 digest에 포함하며 ReconcileAlarmsUseCase가 provider 값을 전달합니다. 이 기존 A05 digest/owner 경로로 pending 언어 차이를 감지하고 원문을 새 fingerprint/로그로 내보내지 않겠습니다. 별도 결함 후보는 서비스와 reconcile이 PlatformDispatcher.instance.locale.languageCode(첫 선호 언어)만 읽지만 MaterialApp은 supportedLocales + 선호 언어 목록을 해석한다는 점입니다. 설치 Flutter의 basicLocaleListResolution 소스에서 [fr, ko]는 KO UI가 가능함을 확인했습니다. 앱/알림은 하나의 같은 지원언어 해석 규칙을 재사용해 [fr,ko], [fr,en], [unsupported only], empty 목록, ko_KR/en_US를 명시 검증하고, 번역 리소스 접근 때문에 domain에 BuildContext/DB 의존성을 넣지 않는 계약으로 보충하겠습니다. + +**Root 조사 보충 답변 원문** + +> 보충채택합니다. 기존A05 normalizedlanguage/contentdigest와A15owner를그대로활용해pendinglocale차이를처리, 새fingerprint원문노출없음. 앱effective locale는MaterialApp지원언어목록+선호언어목록해석과동일규칙을재사용해알림/cold/preDI와맞추세요. 첫preferredlanguage만단독선택하는별도규칙금지. [fr,ko]/[fr,en]/unsupportedonly/empty/ko_KR/en_US 실제지원순서의fallback을정확소스근거로명시하고locale전환이domain상태/콘텐츠revision에영향없음검증. domain BuildContext/DB의존추가없음, presentation/platformadapter가resolvedlanguage/typedrenderinput전달. 이미provider반환지연중locale변경인경우oldcompletion이최신알림현재성을거짓확정하지않는경계는기존A15최신요청/후속reconcile회귀재사용하세요. + +### Q3 — 검증과 실제 완료 경계 + +**Agent 원문** + +> U05 Q3 — 단순 한글 grep이나 ARB 키 개수 일치는 사용자 입력/주석/정책 예외와 완성 문장 누락을 구별하지 못합니다. 권고 완료 기준은 (1) 고정 source→구현 최종 source의 앱 소유 문구 inventory(화면/상태/native 활성·미활성/정책 의존/사용자 데이터 분류)와 ARB placeholder·plural·fallback 정합성, (2) 실제 KOEN widget에서 설정 loading/off/error·초안 validation·반복 검토·준비 초/분/시간·결과 모든 랜덤 ID·백업 비밀번호/취소/stale/부분성공·pre-DI 복구/초기화 상태, (3) locale 변경 중 draft/작업/메시지선택 보존 및 12/24h·지원목록 fallback·raw예외 미노출·알림 private/off + pending locale 갱신 negative 회귀, (4) 작은 화면/200%/Semantics KOEN 표본과 Android/iOS native 실제 활성 문구/권한 설명의 artifact 포함·기기 관찰입니다. 직접 변경으로 생긴 overflow는 고치되 전체 UI/접근성은 U08/U09, 전체 mobile E2E는 T01/T02에 연결하고 미실행 native조건은 OPEN으로 남깁니다. tracked l10n Dart는 정식생성하여 ARB와 함께 반영, ignored .g/.config 등은 제외; 새 네트워크 번역/패키지/DB migration 없이 이 기준으로 동결할까요? + +**Root 원문 — 접근성 소유 항목 정정 포함** + +> Q3 채택합니다. fixed→final문구inventory에서사용자입력/주석/정책미확정/native미활성제외근거명시, 키갯수/한글grep단독성공금지. 실제앱경로KOEN제시상태matrix,랜덤ID전체문구존재/인자/plural검증하되랜덤선택가짜재구현금지. locale변경중작업·draft·선택보존/12·24h/fallback/raw예외/privateoff/pending최신locale회귀필수. preDI는DB불가조건에서도안전하게렌더, O02정책본문미완료를번역누락0이라고숨기지말고명시의존성. native활성문구실제artifact포함/기기관찰과hostwidget분리; 미실행native조건OPEN. 직접overflow수정하고전면레이아웃·접근성U08/U07(접근성대체조작은U07)/U09(오류행동)별도. trackedl10n정식gen/ARB같이, ignored생성제외. 새번역network/package/DBmigration없음. actualQ1Q2Q3와모든root/agent보충·소스검증fallback결과 포함하여동결해주세요. + +Q3 제안의 `전체 UI/접근성은 U08/U09` 연결은 위 root 정정대로 **U07 접근성 대체 조작 / U08 레이아웃 / U09 오류 행동**으로 확정했다. Q2의 기존 예약 알림을 언어 변경 시 전혀 갱신하지 않는 것으로 읽힐 수 있는 부분은 root 답변대로 **다음 정상 reconcile에서 현재 pending digest 차이를 감지·갱신**하는 계약으로 좁혀 명확히 했다. 이 실제 수정 이력은 삭제하거나 처음부터 합의한 것처럼 다시 쓰지 않는다. diff --git a/plans/audit-2026-09-23/state.json b/plans/audit-2026-09-23/state.json index 079b7bb3..cff7ea63 100644 --- a/plans/audit-2026-09-23/state.json +++ b/plans/audit-2026-09-23/state.json @@ -480,10 +480,10 @@ "Actual stale-preview baseline failure and Drift race/rollback/receipt regressions verified", "KO/EN 200% eight widget captures reviewed; source24/commit hashes match", "Generated/local-only/manifest and Python11 passed", - "Exact aa2fda68 remote959 tests/analyze, coverage88.72% (13504/15221), Android APK/notification manifest success; artifact10787843483 identity read back" + "Exact aa2fda68 remote959 tests/analyze, coverage88.72% (13504/15221), Android APK/notification manifest success; artifact10787843483 identity read back", + "A08 aggregate FormSubmission/workflow integrated at126161da with full1018 identical product source and final focused41; A09/D02 remain pending" ], "blockers": [ - "A08 schedule aggregate FormSubmission integration is pending; no atomic aggregate completion claim.", "A09 staging Local Data Store, runtime/session replacement and all-writer fencing are pending. Candidate remains authenticated parsed memory data, not verified staging.", "Revision is checked before OS cleanup and again inside DB transaction; a racing edit during cleanup prevents DB overwrite but may leave OS cleanup pending. Receipt exposes this and retry reconciles current data only.", "D02 damaged-database restore integration is pending.", @@ -491,8 +491,8 @@ ], "sequence": 14, "readback_verified": true, - "body_sha256": "2fb0876dfbbef2ee14038ab85bf1fb4e9bb724197551a76889309c93a19d8c1b", - "issue_body_sha256": "34d437455542e2abd79d7b7abfb7cb735f5a714fab8b15b0bce25e053e0f1139" + "body_sha256": "c72747d9b2647b2b67e6b5b8677461eb5986a0e0e970598fc962b0c12be217d1", + "issue_body_sha256": "c72747d9b2647b2b67e6b5b8677461eb5986a0e0e970598fc962b0c12be217d1" }, { "audit_id": "A08", @@ -505,21 +505,30 @@ "`create_schedule_form_submission_use_case.dart:16-23`; `update_schedule_form_submission_use_case.dart:28-35`; `create_schedule_with_place_use_case.dart:17-20`; `schedule_mutation_alarm_effects_coordinator.dart:41-52`", "일정·장소·준비·revision을 하나의 원자적 작업으로 commit 후 알림 effects 실행. 두 번째 write 실패 시 전체 rollback, 같은 입력 재시도 때 중복 일정 없음. 경쟁의 실제 발생은 별도 재현." ], - "status": "implementing", + "status": "code_verified_ci_device_pending", "agent": "/root/grill_a08", "issue_number": 601, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/601", "decisions_file": "plans/audit-2026-09-23/issues/A08.md", - "implementation_commit": null, - "pr_url": null, - "verification": [], - "blockers": [], + "implementation_commit": "126161dab042365c89983934b1673d860c27d8d3", + "pr_url": "https://github.com/DevKor-github/OnTime-front/pull/585", + "verification": [ + "49 source paths and committed blobs match final freeze", + "Full1018 on identical product source, coverage88.96% (14113/15864); final test-only strengthening focused41 + actual probe1; analyzer clean", + "Actual baseline partial-write failure, atomic rollback/ABA/file reopen/dependency/watch/materialization tests passed", + "KO/EN 200% four synthetic widget captures reviewed; policies and Python11 passed" + ], + "blockers": [ + "Remote CI on final committed source pending", + "A09/D02 must rotate store incarnation for all replacement; staging/runtime and damaged store integration pending", + "Mobile SQLCipher migration and native delivery/file-provider/device checks pending" + ], "sequence": 15, "readback_verified": true, - "body_sha256": "bd5f1b77a400c56f5fb1656412201f72d8288a0a63ed79a699a16f45e7cc1c76", + "body_sha256": "9aeefce75f0f68a1a284cccf6ed09683c6d2b89306f5e0f777ad0a5adbfc82cf", "implementation_agent": "/root/implement_a08", "implementation_plan": "plans/audit-2026-09-23/a08-implementation-preflight.md", - "issue_body_sha256": "bd5f1b77a400c56f5fb1656412201f72d8288a0a63ed79a699a16f45e7cc1c76" + "issue_body_sha256": "9aeefce75f0f68a1a284cccf6ed09683c6d2b89306f5e0f777ad0a5adbfc82cf" }, { "audit_id": "A09", @@ -532,7 +541,7 @@ "`backup_service.dart:146-196`; `my_data_screen.dart:159-165`; `preparation_with_time_local_data_source.dart`; `early_start_session_local_data_source.dart`; session use case의 restore 경로", "복원 orchestration에서 활동 중 세션/구독/알림을 정리하고 DB 교체 후 재시작. 같은 ID의 진행 중 일정 복원 후 진행 이력이 되살아나지 않아야 함. 실패 시 기존 DB 보존." ], - "status": "ready", + "status": "implementing", "agent": "/root/grill_a09", "issue_number": 602, "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/602", @@ -544,7 +553,8 @@ "sequence": 16, "readback_verified": true, "body_sha256": "97c77e8e911aa351002e7979674507f4586ef0fabd96ad3cf91343e902f2638a", - "issue_body_sha256": "97c77e8e911aa351002e7979674507f4586ef0fabd96ad3cf91343e902f2638a" + "issue_body_sha256": "97c77e8e911aa351002e7979674507f4586ef0fabd96ad3cf91343e902f2638a", + "implementation_agent": "/root/implement_a09" }, { "audit_id": "D01", @@ -1073,16 +1083,18 @@ "영어 locale에서 한국어가 섞이는 설정·준비·결과·백업·오류 문구를 정리한다.", "앱은 EN/KO 지원(`app.dart:203-204`), 여러 화면과 time_format은 한국어 고정. ARB와 locale-aware 시간 단위/12·24시간 표기로 통일." ], - "status": "awaiting_grill", - "agent": null, - "issue_number": null, - "issue_url": null, + "status": "issue_created", + "agent": "/root/grill_u05", + "issue_number": 626, + "issue_url": "https://github.com/DevKor-github/OnTime-front/issues/626", "decisions_file": "plans/audit-2026-09-23/issues/U05.md", "implementation_commit": null, "pr_url": null, "verification": [], "blockers": [], - "sequence": 39 + "sequence": 39, + "readback_verified": true, + "issue_body_sha256": "b2f454c66106c34520c96bada18b9dc74b47f74b82aab9a68a474bc6c9309cbb" }, { "audit_id": "U06",

V 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

!G(7&+%XL{PNXyc@Uw@w~EemjB?eN7KYo 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 `zhV0Fnod{6ARF`dA&P`J$~Oc&Or+)9paAN3 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%0Dk{GK-j?{|NACFeUE4Rg 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