Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agent/tasks/hourleaf-siri-v1/evidence.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
]
}
43 changes: 26 additions & 17 deletions .agent/tasks/hourleaf-siri-v1/evidence.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.

Expand All @@ -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.
29 changes: 19 additions & 10 deletions .agent/tasks/hourleaf-siri-v1/implementation-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
34 changes: 24 additions & 10 deletions .agent/tasks/hourleaf-siri-v1/problems.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 3 additions & 3 deletions .agent/tasks/hourleaf-siri-v1/raw/focused-tests-summary.json
Original file line number Diff line number Diff line change
@@ -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"
}
9 changes: 7 additions & 2 deletions .agent/tasks/hourleaf-siri-v1/raw/verification-receipt.txt
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
4 changes: 2 additions & 2 deletions .agent/tasks/hourleaf-siri-v1/verdict.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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."
}
11 changes: 6 additions & 5 deletions Hourleaf/AppIntents/RecordTimeIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -251,7 +252,7 @@ struct RecordCreditTimeIntent: AppIntent {

static var openAppWhenRun: Bool { false }
static var authenticationPolicy: IntentAuthenticationPolicy {
.requiresAuthentication
.alwaysAllowed
}

@Parameter(
Expand Down
6 changes: 3 additions & 3 deletions HourleafTests/AppIntentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions HourleafTests/Watch/WatchTimeEntryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
Expand Down
4 changes: 2 additions & 2 deletions HourleafWatch/WatchRecordTimeIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Loading