diff --git a/.agent/tasks/hourleaf-siri-v1/evidence.json b/.agent/tasks/hourleaf-siri-v1/evidence.json index cf0647e..2e49468 100644 --- a/.agent/tasks/hourleaf-siri-v1/evidence.json +++ b/.agent/tasks/hourleaf-siri-v1/evidence.json @@ -20,12 +20,12 @@ { "id": "AC4", "status": "UNKNOWN", - "proof": ["raw/device-shortcuts-receipt.txt"] + "proof": ["raw/device-shortcuts-receipt.txt", "raw/authentication-policy-receipt.txt"] }, { "id": "AC5", "status": "PASS", - "proof": ["raw/verification-receipt.txt", "raw/site-verification.txt"] + "proof": ["raw/verification-receipt.txt", "raw/authentication-policy-receipt.txt", "raw/site-verification.txt"] } ] } diff --git a/.agent/tasks/hourleaf-siri-v1/evidence.md b/.agent/tasks/hourleaf-siri-v1/evidence.md index 885ab9c..45d4399 100644 --- a/.agent/tasks/hourleaf-siri-v1/evidence.md +++ b/.agent/tasks/hourleaf-siri-v1/evidence.md @@ -2,11 +2,11 @@ ## Final verdict -UNKNOWN. The source, localized metadata, current iPhone Shortcut cards, release -build, automated tests, and public guidance are repaired and verified. The one -remaining acceptance gate is a direct owner-voice Siri invocation on the -unlocked physical iPhone; iPhone Mirroring cannot prove that authenticated -path. +UNKNOWN. The card-name/discovery repair is verified, and the newly observed +execution failure has a bounded authentication-policy fix with green source, +compiled-metadata, Release, and regression evidence. The production Store build +still has the former policy. A disposable physical build must reach the +duration prompt before the source fix can be called device-proven. ## AC1 — PASS: discoverable metadata @@ -29,19 +29,27 @@ path. and the existing validated command/repository path. - The service card retains fixed kind `Служение`; credit remains a distinct fixed-kind action. No persistence or schema code changed. -- Focused App Intent tests passed 18/18; the complete Hourleaf unit/integration - suite passed 511/511. +- Focused iPhone App Intent plus Watch contract tests passed 26/26; the complete + Hourleaf unit/integration suite passed 511/511. +- Compiled Release metadata declares background execution and authentication + policy `0` for the two iPhone and two Watch recording actions. -## AC4 — UNKNOWN: direct Siri invocation +## AC4 — UNKNOWN: fixed binary on a physical device - Read-only device inspection confirmed Hourleaf 1.0.2 (13) on an iPhone 15 Pro with iOS 26.6 and an Apple Watch Series 10 with watchOS 26.6. - The physical iPhone visibly contains enabled cards named exactly `Запиши служение` and `Запиши кредит`. The service action was refreshed from the currently installed Hourleaf action gallery. -- Mirrored execution returned the iOS message `Это действие не разрешено` while - the mirrored session controlled an otherwise locked handset. That result is - not evidence about direct Siri execution on an unlocked device. +- The owner then invoked the direct Siri path and reported `Что-то пошло не + так`; the currently installed 1.0.2 (13) binary therefore fails this + acceptance criterion. +- Mirrored execution of the same production action returned the more specific + iOS message `Это действие не разрешено` before duration collection. Shortcut + privacy readback showed Hourleaf access and locked execution already enabled. +- The replacement policy is compiled and automatically verified, but is not in + the installed Store build. A disposable physical build remains the decisive + no-save check. - No duration was supplied and no ledger entry was written. A direct spoken invocation remains the only missing physical result. @@ -55,10 +63,11 @@ path. - No dependency, data model, entitlement, privacy manifest, account, analytics, bundle identifier, Store build, app container, or ledger changed. -## Root cause and forward fix +## Root causes and forward fixes -The existing service Shortcut card was named `Записать время`, while the phrase -shown to the owner was `Запиши служение`. Siri invokes a user-created Shortcut -by its card name. The physical card is now renamed, and the localized service -action title is aligned with that promoted name so future setup does not create -the same mismatch. +1. Discovery: the service card was named `Записать время`, while the promoted + phrase was `Запиши служение`. The card and localized action title now match. +2. Execution: the recording intents explicitly required authentication even + though the Shortcuts flow is intended to work hands-free and while locked. + The four iPhone/Watch service and credit actions now use `.alwaysAllowed` + without exposing any ledger content or changing the validated write path. diff --git a/.agent/tasks/hourleaf-siri-v1/implementation-notes.md b/.agent/tasks/hourleaf-siri-v1/implementation-notes.md index ceebd23..5f15d4f 100644 --- a/.agent/tasks/hourleaf-siri-v1/implementation-notes.md +++ b/.agent/tasks/hourleaf-siri-v1/implementation-notes.md @@ -4,10 +4,9 @@ instructed the owner to invoke a custom Shortcut named `Запиши служение`. Siri runs a user-created Shortcut by its exact card name, so the mismatch was sufficient to make the documented app-name-free phrase undiscoverable. -- The smallest forward fix aligns the service action title with the promoted +- The first forward fix aligned the service action title with the promoted Shortcut title in EN/RU/UK. The action identifier, parameters, persistence - path, authentication policy, Core Data model, and bundle identifiers are - unchanged. + path, Core Data model, and bundle identifiers remained unchanged. - A regression test parses all three app localizations and requires `intent.record.title` to equal `intent.shortcut.add_service`. - The built-in App Shortcut phrases continue to include the application name, @@ -18,20 +17,30 @@ physical iPhone. Both cards now read exactly `Запиши служение` and `Запиши кредит`; the service action retains fixed kind `Служение`, asks for one duration, and does not contain a preset date. -- Running an authenticated action while iPhone Mirroring controlled the locked - handset returned the system message `Это действие не разрешено`. This is not - treated as evidence about an unlocked Siri invocation. The product keeps - `.requiresAuthentication`; weakening the existing privacy contract was not - justified. +- A direct owner test then reached the Shortcut but returned the generic Siri + failure `Что-то пошло не так`. Running the same installed action while + iPhone Mirroring controlled the handset returned `Это действие не разрешено` + before parameter collection. The card's privacy controls already allowed + Hourleaf and locked execution, isolating the explicit intent authentication + policy as the next executable boundary. +- Service and credit recording now declare `.alwaysAllowed` in both the iPhone + and Watch binaries. These actions only add a validated record; they never + reveal notes, history, totals, or reports. `openAppWhenRun` stays false, Core + Data retains complete-until-first-authentication file protection, and the + normal command validation still rejects empty, invalid, or excessive time. +- Fresh compiled iPhone and Watch App Intents metadata emits authentication + policy `0` for all four record actions, with the policy explicitly declared + and background execution preserved. - The public EN/RU/UK guide now explains the legacy action title, exact card names, and one-time run. Support no longer implies that an iPhone-created Shortcut is executable on Apple Watch; Watch users are directed to the native Hourleaf watch app. -- No test entry was saved and no Hourleaf ledger, app container, account, - entitlement, dependency, or Store build was changed. +- No test entry was saved and no Hourleaf ledger, production app container, + account, entitlement, dependency, schema, or Store build was changed. ## Primary references - https://support.apple.com/guide/shortcuts/run-shortcuts-with-siri-apd07c25bb38/ios - https://support.apple.com/guide/shortcuts/run-shortcuts-from-apple-watch-apd5888b0858/ios - https://developer.apple.com/documentation/appintents/intentauthenticationpolicy/requiresauthentication +- https://developer.apple.com/documentation/appintents/intentauthenticationpolicy/alwaysallowed diff --git a/.agent/tasks/hourleaf-siri-v1/problems.md b/.agent/tasks/hourleaf-siri-v1/problems.md index 621f7f7..12b89e3 100644 --- a/.agent/tasks/hourleaf-siri-v1/problems.md +++ b/.agent/tasks/hourleaf-siri-v1/problems.md @@ -1,14 +1,28 @@ -# Remaining physical gate +# Direct Siri execution failure + +The card-name repair proved discovery but exposed a second, independent +failure. After Siri reached the owner-created Shortcut, the owner reported the +system response `Что-то пошло не так`. A manual run of the same installed +Hourleaf action through iPhone Mirroring stopped even earlier with +`Это действие не разрешено`, before the duration prompt appeared. -The implementation and setup repair are complete, but direct Siri execution is -still unverified. iPhone Mirroring controls a locked handset and returned -`Это действие не разрешено`; this cannot establish how Siri behaves when the -owner invokes the exact phrase on the unlocked physical iPhone. +Read-only inspection ruled out the visible configuration: -The decisive check is intentionally non-destructive: +- the cards are named exactly `Запиши служение` and `Запиши кредит`; +- the service card still points to Hourleaf's `RecordTimeIntent`; +- duration is configured as `Ask Each Time` and no date is preset; +- the Shortcut privacy page allows Hourleaf access and execution while locked. -1. Unlock the iPhone. -2. Say `Siri, запиши служение` directly to the iPhone. -3. If Siri asks for a duration, cancel instead of supplying one. +The remaining pre-parameter boundary was Hourleaf's explicit +`.requiresAuthentication` policy. Recording exposes no ledger contents, and +the store remains protected until the first device unlock, so service and +credit recording now use `.alwaysAllowed` on both iPhone and Apple Watch. +Validation, fixed entry kind, persistence, and the no-open-app behavior remain +unchanged. + +# Remaining physical gate -Asking for the duration proves discovery without creating a ledger entry. +The installed Store build still contains the former authentication policy. A +separate disposable build must first reach the duration prompt on the physical +iPhone without saving an entry. Only a later owner-approved Store upload can +put the same fix into the production Hourleaf binary. diff --git a/.agent/tasks/hourleaf-siri-v1/raw/authentication-policy-receipt.txt b/.agent/tasks/hourleaf-siri-v1/raw/authentication-policy-receipt.txt new file mode 100644 index 0000000..687350f --- /dev/null +++ b/.agent/tasks/hourleaf-siri-v1/raw/authentication-policy-receipt.txt @@ -0,0 +1,30 @@ +Authentication-policy verification at 2026-08-24T15:50:00+03:00. + +Source contract: + +- iPhone service: `.alwaysAllowed`, `openAppWhenRun=false`. +- iPhone credit: `.alwaysAllowed`, `openAppWhenRun=false`. +- Watch service: `.alwaysAllowed`, `openAppWhenRun=false`. +- Watch credit: `.alwaysAllowed`, `openAppWhenRun=false`. + +Compiled generic iOS Release product: + +- `Hourleaf.app/Metadata.appintents/extract.actionsdata` contains explicit + authentication policy `0` for `RecordTimeIntent` and + `RecordCreditTimeIntent`. +- Embedded `HourleafWatch.app/Metadata.appintents/extract.actionsdata` contains + explicit authentication policy `0` for `WatchRecordServiceTimeIntent` and + `WatchRecordCreditTimeIntent`. +- iPhone and Watch metadata extraction completed without processor errors. + +Verification: + +- Focused tests: 26/26 PASS. +- Complete HourleafTests: 511/511 PASS. +- Generic iOS Release build: PASS. +- Release-readiness guard: PASS. +- Release-readiness guard self-test: PASS. +- App/extension/Watch privacy and Info plist lint: PASS. + +No production install, Store upload, ledger mutation, schema change, or +entitlement change occurred during this receipt. diff --git a/.agent/tasks/hourleaf-siri-v1/raw/device-shortcuts-receipt.txt b/.agent/tasks/hourleaf-siri-v1/raw/device-shortcuts-receipt.txt index eba4318..1176a6b 100644 --- a/.agent/tasks/hourleaf-siri-v1/raw/device-shortcuts-receipt.txt +++ b/.agent/tasks/hourleaf-siri-v1/raw/device-shortcuts-receipt.txt @@ -9,3 +9,12 @@ Physical-device readback at 2026-08-24T11:49:00+03:00. - Refreshed service action parameters: fixed kind `Служение`, duration `Ask Each Time`, optional date. - No duration was supplied and no ledger entry was written. - Mirrored execution returned `Это действие не разрешено`; an unlocked owner-voice Siri invocation remains the decisive physical gate. + +Follow-up owner result at 2026-08-24: + +- Direct Siri reached the configured Shortcut but returned `Что-то пошло не так`. +- Shortcut Details > Privacy visibly had `Run While Locked` and Hourleaf access enabled. +- A repeated mirrored run stopped with `Это действие не разрешено` before the + duration prompt, consistent with the installed binary's explicit + `.requiresAuthentication` policy. +- The production Hourleaf 1.0.2 (13) app was not replaced, removed, or modified. diff --git a/.agent/tasks/hourleaf-siri-v1/raw/focused-tests-summary.json b/.agent/tasks/hourleaf-siri-v1/raw/focused-tests-summary.json index 07f09bd..f233ab5 100644 --- a/.agent/tasks/hourleaf-siri-v1/raw/focused-tests-summary.json +++ b/.agent/tasks/hourleaf-siri-v1/raw/focused-tests-summary.json @@ -1,10 +1,10 @@ { "result": "Passed", - "totalTestCount": 18, - "passedTests": 18, + "totalTestCount": 26, + "passedTests": 26, "failedTests": 0, "skippedTests": 0, - "target": "HourleafTests/AppIntentTests", + "target": "HourleafTests/AppIntentTests + HourleafTests/WatchTimeEntryTests", "device": "iPhone 17 simulator", "osVersion": "26.5" } diff --git a/.agent/tasks/hourleaf-siri-v1/raw/verification-receipt.txt b/.agent/tasks/hourleaf-siri-v1/raw/verification-receipt.txt index 20914d8..9667641 100644 --- a/.agent/tasks/hourleaf-siri-v1/raw/verification-receipt.txt +++ b/.agent/tasks/hourleaf-siri-v1/raw/verification-receipt.txt @@ -1,9 +1,14 @@ -Verified at 2026-08-24T11:51:13+03:00. +Verified again at 2026-08-24T15:50:00+03:00 after the direct Siri execution +failure was reported. -- Focused AppIntentTests: 18/18 PASS. +- Focused AppIntentTests + WatchTimeEntryTests: 26/26 PASS. - Complete HourleafTests: 511/511 PASS. - Generic iOS Release build with signing disabled: BUILD SUCCEEDED. - App Intents metadata processor and App Shortcut localization validation: PASS. +- Compiled iPhone recording metadata: explicit authentication policy `0`, + `openAppWhenRun=false` for service and credit. +- Compiled Watch recording metadata: explicit authentication policy `0` for + service and credit. - Compiled service action titles: EN `Record service`; RU `Запиши служение`; UK `Запиши служіння`. - Compiled built-in service phrases retain the required application-name token in EN/RU/UK. - Release-readiness guard: PASS. diff --git a/.agent/tasks/hourleaf-siri-v1/verdict.json b/.agent/tasks/hourleaf-siri-v1/verdict.json index ff98bf2..89ec4f8 100644 --- a/.agent/tasks/hourleaf-siri-v1/verdict.json +++ b/.agent/tasks/hourleaf-siri-v1/verdict.json @@ -1,7 +1,7 @@ { "task": "hourleaf-siri-v1", "verdict": "UNKNOWN", - "review_mode": "final-read-only-with-physical-owner-gate", + "review_mode": "fix-verified-locally-with-physical-binary-gate", "findings": [], "acceptance": { "AC1": "PASS", @@ -10,5 +10,5 @@ "AC4": "UNKNOWN", "AC5": "PASS" }, - "residual_risk": "The exact Shortcut cards and current action metadata are verified, but a direct owner-voice Siri invocation on the unlocked physical iPhone has not yet been observed. Mirrored execution is not an equivalent authentication path." + "residual_risk": "The installed Store binary fails before duration collection and still contains the former authentication policy. The replacement policy is source-, metadata-, Release-, and regression-verified but remains unproven on a physical device until a disposable signed build reaches the duration prompt without saving an entry." } diff --git a/Hourleaf/AppIntents/RecordTimeIntent.swift b/Hourleaf/AppIntents/RecordTimeIntent.swift index 146338b..7c50b65 100644 --- a/Hourleaf/AppIntents/RecordTimeIntent.swift +++ b/Hourleaf/AppIntents/RecordTimeIntent.swift @@ -114,10 +114,11 @@ struct RecordTimeIntent: AppIntent { static var openAppWhenRun: Bool { false } static var authenticationPolicy: IntentAuthenticationPolicy { - // Ordinary authenticated execution is sufficient for the iPhone - // Shortcuts and Siri surfaces. Apple Watch requires a watchOS app of - // its own; a paired iPhone intent cannot execute on the Watch. - .requiresAuthentication + // Recording adds a validated entry but never reveals ledger contents. + // Allowing background execution keeps Siri and Shortcuts usable from + // the lock screen while Core Data still enforces the normal command + // validation and file-protection boundaries. + .alwaysAllowed } @Parameter(title: "intent.record.kind", default: .service) @@ -251,7 +252,7 @@ struct RecordCreditTimeIntent: AppIntent { static var openAppWhenRun: Bool { false } static var authenticationPolicy: IntentAuthenticationPolicy { - .requiresAuthentication + .alwaysAllowed } @Parameter( diff --git a/HourleafTests/AppIntentTests.swift b/HourleafTests/AppIntentTests.swift index 8189ea5..973a9a9 100644 --- a/HourleafTests/AppIntentTests.swift +++ b/HourleafTests/AppIntentTests.swift @@ -219,11 +219,11 @@ final class AppIntentTests: XCTestCase { XCTAssertEqual(after.reportSnapshots.count, 1) } - func testIntentExecutionPoliciesStaySeparated() { + func testRecordIntentsAllowHandsFreeExecutionWithoutOpeningApp() { XCTAssertFalse(RecordTimeIntent.openAppWhenRun) - XCTAssertEqual(RecordTimeIntent.authenticationPolicy, .requiresAuthentication) + XCTAssertEqual(RecordTimeIntent.authenticationPolicy, .alwaysAllowed) XCTAssertFalse(RecordCreditTimeIntent.openAppWhenRun) - XCTAssertEqual(RecordCreditTimeIntent.authenticationPolicy, .requiresAuthentication) + XCTAssertEqual(RecordCreditTimeIntent.authenticationPolicy, .alwaysAllowed) XCTAssertTrue(OpenQuickEntryIntent.openAppWhenRun) XCTAssertEqual(OpenQuickEntryIntent.authenticationPolicy, .alwaysAllowed) } diff --git a/HourleafTests/Watch/WatchTimeEntryTests.swift b/HourleafTests/Watch/WatchTimeEntryTests.swift index f30791e..1015f32 100644 --- a/HourleafTests/Watch/WatchTimeEntryTests.swift +++ b/HourleafTests/Watch/WatchTimeEntryTests.swift @@ -130,9 +130,9 @@ final class WatchTimeEntryTests: XCTestCase { XCTAssertTrue(source.contains("intent: WatchRecordServiceTimeIntent()")) XCTAssertTrue(source.contains("intent: WatchRecordCreditTimeIntent()")) XCTAssertFalse(source.contains("WatchRecordTimeIntent(kind:")) - XCTAssertFalse(source.contains(".alwaysAllowed")) + XCTAssertFalse(source.contains(".requiresAuthentication")) XCTAssertEqual( - source.components(separatedBy: ".requiresAuthentication").count - 1, + source.components(separatedBy: ".alwaysAllowed").count - 1, 2 ) } diff --git a/HourleafWatch/WatchRecordTimeIntent.swift b/HourleafWatch/WatchRecordTimeIntent.swift index 966938b..68cb6ef 100644 --- a/HourleafWatch/WatchRecordTimeIntent.swift +++ b/HourleafWatch/WatchRecordTimeIntent.swift @@ -15,7 +15,7 @@ struct WatchRecordServiceTimeIntent: AppIntent { IntentDescription("watch.intent.record.description") } static var openAppWhenRun: Bool { false } - static var authenticationPolicy: IntentAuthenticationPolicy { .requiresAuthentication } + static var authenticationPolicy: IntentAuthenticationPolicy { .alwaysAllowed } @Parameter( title: "watch.intent.duration", @@ -65,7 +65,7 @@ struct WatchRecordCreditTimeIntent: AppIntent { IntentDescription("watch.intent.record.description") } static var openAppWhenRun: Bool { false } - static var authenticationPolicy: IntentAuthenticationPolicy { .requiresAuthentication } + static var authenticationPolicy: IntentAuthenticationPolicy { .alwaysAllowed } @Parameter( title: "watch.intent.duration",