Skip to content
Merged
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
12 changes: 6 additions & 6 deletions Delta.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Packaging/Delta.entitlements;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Packaging/Delta.app.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
Expand All @@ -1010,7 +1010,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Packaging/Delta.entitlements;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Packaging/Delta.app.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
Expand Down Expand Up @@ -1122,7 +1122,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = "$(DELTA_FSKIT_CODE_SIGN_ENTITLEMENTS)";
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
DELTA_FSKIT_CODE_SIGN_ENTITLEMENTS = Packaging/DeltaTimeMachineFS.entitlements;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Packaging/DeltaTimeMachineFS.Info.plist;
Expand Down Expand Up @@ -1238,7 +1238,7 @@
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Packaging/DeltaTimeMachineHelper.Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -1274,7 +1274,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = "$(DELTA_FSKIT_CODE_SIGN_ENTITLEMENTS)";
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
DELTA_FSKIT_CODE_SIGN_ENTITLEMENTS = Packaging/DeltaTimeMachineFS.entitlements;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Packaging/DeltaTimeMachineFS.Info.plist;
Expand Down Expand Up @@ -1309,7 +1309,7 @@
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 13;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = Packaging/DeltaTimeMachineHelper.Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
2 changes: 2 additions & 0 deletions Documentation/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Scheduled-service registration is gated by a fingerprint of the bundled agent an

Time Machine system-support registration is likewise bound to a streaming fingerprint of the normalized installed bundle path, user service, privileged helper, property lists, and FSKit extension. Production registration and privileged mutations require the exact `/Applications/Delta.app` installation, so a renamed acceptance copy, DerivedData product, `dist` artifact, or worktree build cannot replace the production Background Items records. Ordinary disconnect remains available from a noncanonical copy so cleanup cannot be stranded. The path witness makes a relocated app detectable even when every embedded byte is unchanged and macOS still retains the earlier bundle location. An idle installed-app update performs the public replacement once, then proves that the exact new helper can launch before the installed fingerprint becomes current. If Service Management still points at an older or moved app, reports an enabled item that cannot run, or launches a stale helper, the proof fails closed and the explicit **Set Up** action re-registers the affected helper and user service through the same public lifecycle. A component newly registered during that request is not torn down merely because its sibling was stale, avoiding a second privileged approval cycle. macOS remains authoritative and may request renewed Login Items approval. Delta never edits launchd or approval databases directly.

Release acceptance uses that same production verifier rather than a test-only launch. On a clean acceptance host, both Time Machine services must begin unregistered, the exact notarized candidate must be installed at `/Applications/Delta.app`, an administrator must approve the launch daemon through System Settings as Apple's `SMAppService` contract requires, and both public registrations must become enabled. Delta then opens the helper's production Mach service and accepts readiness only when the running process returns the Security code hash of the helper embedded in that exact app. The evidence is bound to source commit, version, build, app CDHash, helper CDHash, canonical path, clean initial state, and supported unregister cleanup; production publishing rejects missing, stale, or mismatched evidence. Headless automation cannot substitute direct execution, `launchctl`, private Background Task Management changes, or a synthetic approval for that native administrator boundary.

## Data flow

1. A native file picker records security-scoped bookmarks for selected sources, destinations, and restore targets.
Expand Down
1 change: 1 addition & 0 deletions Documentation/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Delta 0.4.0 adds native Time Machine-format destinations:
- Requires a short authenticated readiness response from the exact embedded privileged helper before recording system support as current or mounting a disk, so an enabled-but-unlaunchable macOS background item fails before remote writes and routes to native Set Up recovery instead of consuming the twelve-minute mutation deadline.
- Keeps a partial FSKit/APFS mount in an explicit cleanup-only state: storage telemetry can no longer erase a system-connection failure, and no UI or command surface offers Back Up Now until macOS returns the exact Time Machine destination identity.
- Places the privileged setup daemon in Apple's current `SMAppService` executable layout and rejects the obsolete `SMJobBless` helper location, preventing update-time launch constraints from being bound to a legacy bundle structure.
- Adds a fail-closed clean-install release gate for the exact notarized app: after native administrator approval, Delta must launch and authenticate the embedded privileged helper by Security code hash, and candidate-bound evidence must match the app, helper, source commit, immutable build, and canonical `/Applications/Delta.app` path before publishing.

This release also restores live Activity output following:

Expand Down
11 changes: 11 additions & 0 deletions Documentation/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,21 @@ The Keychain profile name is not product metadata; the existing `Reccy Notary` p

Finalization writes public candidates under `dist/updates`, private notarization evidence under `dist/notarization`, and private dSYMs under `dist/symbols`.

Before publishing, install the exact finalized candidate and prove its privileged Time Machine support on a clean acceptance host:

```sh
Scripts/install-app.sh dist/Delta.app
Scripts/run-installed-time-machine-system-support-acceptance.sh /Applications/Delta.app
```

The host must begin with both Delta Time Machine Service Management items unregistered. Apple's public `SMAppService` contract requires an administrator to approve a launch daemon in System Settings before it is eligible to run, so this is deliberately an interactive installed-candidate gate rather than a headless CI simulation. The script waits for the native approval, requires both items to become enabled, authenticates the running helper against the exact embedded helper code hash, unregisters both items through public APIs, and writes candidate-bound evidence under `dist/time-machine-system-support`. It never runs a helper directly, edits Background Task Management or launchd state, or accepts a renamed, `dist`, DerivedData, archive, or worktree app.

## Publish

`Scripts/publish-release.sh` performs the publishing transaction. Before contacting GitHub it re-runs the history/security audit, verifies the complete artifact graph, and requires `Scripts/verify-production-readiness.sh` to pass for the exact installed candidate, current manual acceptance report, and genuine external-backend evidence. It then creates a draft with the six intended public assets, downloads those bytes into a new temporary directory, repeats the full signature/notarization/Gatekeeper/ZIP/DMG/provenance/Sparkle verification, and only then makes the release public and latest.

Production readiness also verifies the clean-install Time Machine system-support report against the exact installed app, source commit, immutable build identity, app CDHash, helper CDHash, enabled registration states, authenticated readiness result, and supported cleanup state. A report from a different candidate or a development machine with retained registration state cannot satisfy the gate.

One marketing-version/build-number pair identifies one immutable signed app. `Scripts/install-app.sh` refuses to replace an installed Delta app with different signed bytes carrying the same `CFBundleShortVersionString` and `CFBundleVersion`; advance `CURRENT_PROJECT_VERSION` before building a replacement. This prevents local build/test churn from presenting macOS Service Management with two helper executables under one release identity and makes installed acceptance match the update topology users receive through Sparkle.

## GitHub Actions
Expand Down
7 changes: 4 additions & 3 deletions Documentation/VERIFICATION_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ Date: 19 July 2026

Host: macOS 26.5.2 (25F84)

Candidate: Delta 0.4.0 (12)
Candidate: Delta 0.4.0 (13)

Status: exact-candidate verification contract

## Outcome

Delta's source, deterministic acceptance harnesses, Developer ID build, stable installer, notarization flow, Sparkle packaging, privacy controls, and production-readiness checks are covered by the release contract below. The source identifies the next candidate as `0.4.0` (12).
Delta's source, deterministic acceptance harnesses, Developer ID build, stable installer, notarization flow, Sparkle packaging, privacy controls, and production-readiness checks are covered by the release contract below. The source identifies the next candidate as `0.4.0` (13).

A release must not be merged, tagged, or published until the complete exact-commit gate, separate app and DMG notarization, stapling, Gatekeeper checks, signed-update installation, manual acceptance matrix, and required genuine external-provider evidence have all passed for this version. Generated evidence under `dist/` is authoritative for the commit, installed path, CDHash, notarization IDs, artifact hashes, command results, and acceptance status; this tracked report deliberately does not duplicate volatile identity values or claim that an unverified source commit has passed.

Expand All @@ -34,6 +34,7 @@ A release must not be merged, tagged, or published until the complete exact-comm
- Kept scheduled notification submission inside the short-lived agent lifetime until macOS acknowledges the request, with bounded timeout and failure evidence.
- Removed Intel-sensitive `pipefail` handling from bundled-tool validation and made the crash-marker scan portable to clean macOS runners without ripgrep.
- Added Delta-managed Time Machine-format destinations backed by a provisioned FSKit extension, bounded local cache, authenticated remote generations, verified recovery-key retention, safe reconnect and removal, and the existing native Permissions guidance.
- Added a clean first-registration Time Machine system-support acceptance gate that uses the production Service Management and authenticated XPC path, binds evidence to the exact notarized installed candidate and helper code hashes, and prevents publishing from treating retained development registration state or a transient build location as release proof.

## Automated verification contract

Expand Down Expand Up @@ -68,7 +69,7 @@ Finalization requires separate accepted Apple submissions for the app archive an

## Required release handoff

1. Build, install, and complete all exact-commit automated, signing, Apple notarization, stapling, Gatekeeper, and evidence checks for `0.4.0` (12).
1. Build, install, and complete all exact-commit automated, signing, Apple notarization, stapling, Gatekeeper, and evidence checks for `0.4.0` (13).
2. Complete every row of the exact-candidate manual acceptance matrix, including a real signed Sparkle upgrade and system-delivered notifications.
3. Provide and exercise genuine external mounted SMB or NFS, non-local SFTP, and non-local S3-compatible fixtures, plus any additional backend families intended for this release.
4. Obtain a fully passing production-readiness result before merging, tagging `v0.4.0`, or publishing.
20 changes: 20 additions & 0 deletions Scripts/collect-release-evidence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ LATEST="$OUTPUT_DIR/latest.md"
INFO_PLIST="$APP_PATH/Contents/Info.plist"
GATE_STATUS_FILE="$ROOT_DIR/dist/release-evidence/automated-gate-status"
MANUAL_ACCEPTANCE_REPORT="${DELTA_MANUAL_ACCEPTANCE_REPORT:-$ROOT_DIR/dist/manual-acceptance/latest.md}"
TIME_MACHINE_SYSTEM_EVIDENCE="${DELTA_TIME_MACHINE_SYSTEM_ACCEPTANCE_EVIDENCE:-$ROOT_DIR/dist/time-machine-system-support/latest.txt}"

plist_value() {
local key="$1"
Expand Down Expand Up @@ -141,6 +142,14 @@ if [[ -n "$IDENTITY_ACCEPTANCE_APP_PATH" ]]; then
append_command "Service Management Lifecycle Acceptance" "$ROOT_DIR/Scripts/run-installed-service-management-acceptance.sh" "$IDENTITY_ACCEPTANCE_APP_PATH"
append_command "Installed Preferences Acceptance" "$ROOT_DIR/Scripts/run-installed-preferences-acceptance.sh" "$IDENTITY_ACCEPTANCE_APP_PATH"
append_command "Installed App Smoke Verification" "$ROOT_DIR/Scripts/verify-installed-app.sh" "$INSTALLED_APP_PATH"
append_command "Time Machine System Support Acceptance Verification" \
"$ROOT_DIR/Scripts/verify-time-machine-system-support-evidence.sh" \
"$IDENTITY_ACCEPTANCE_APP_PATH" \
"$TIME_MACHINE_SYSTEM_EVIDENCE"
if [[ -e "$TIME_MACHINE_SYSTEM_EVIDENCE" ]]; then
append_command "Time Machine System Support Acceptance Evidence" \
/bin/cat "$TIME_MACHINE_SYSTEM_EVIDENCE"
fi
EXTERNAL_ACCEPTANCE_APP_PATH="$INSTALLED_APP_PATH"
else
append_command "Scheduled Backups Acceptance" missing_installed_identity
Expand Down Expand Up @@ -244,12 +253,22 @@ fi
} >>"$OUTPUT"
/bin/rm -f "$EXTERNAL_ACCEPTANCE_OUTPUT"

TIME_MACHINE_SYSTEM_ACCEPTANCE_PASSED="No"
if [[ -n "$IDENTITY_ACCEPTANCE_APP_PATH" ]] \
&& "$ROOT_DIR/Scripts/verify-time-machine-system-support-evidence.sh" \
"$IDENTITY_ACCEPTANCE_APP_PATH" \
"$TIME_MACHINE_SYSTEM_EVIDENCE" >/dev/null 2>&1
then
TIME_MACHINE_SYSTEM_ACCEPTANCE_PASSED="Yes"
fi

READY_FOR_EXTERNAL_DISTRIBUTION="No"
if [[ "$AUTOMATED_GATE_STATUS" == "Passed" \
&& "$AUTOMATED_GATE_APP_HASH_MATCH" == "Yes" \
&& "$MANUAL_MATRIX_PASSED" == "Yes" \
&& "$MANUAL_REPORT_CURRENT" == "Yes" \
&& "$EXTERNAL_ACCEPTANCE_PASSED" == "Yes" \
&& "$TIME_MACHINE_SYSTEM_ACCEPTANCE_PASSED" == "Yes" \
&& "$NOTARIZATION_COMPLETE" == "Yes" ]]
then
READY_FOR_EXTERNAL_DISTRIBUTION="Yes"
Expand All @@ -269,6 +288,7 @@ Manual matrix passed: $MANUAL_MATRIX_PASSED
- Manual report matches git commit: $MANUAL_REPORT_CURRENT
- Manual matrix passed: $MANUAL_MATRIX_PASSED
- Real external backend acceptance passed: $EXTERNAL_ACCEPTANCE_PASSED
- Clean-install Time Machine system support passed: $TIME_MACHINE_SYSTEM_ACCEPTANCE_PASSED
- Developer ID notarization complete: $NOTARIZATION_COMPLETE
- Ready for external distribution: $READY_FOR_EXTERNAL_DISTRIBUTION
EOF
Expand Down
15 changes: 15 additions & 0 deletions Scripts/doctor-production-readiness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ MANUAL_REPORT="${DELTA_DOCTOR_MANUAL_ACCEPTANCE_REPORT:-$ROOT_DIR/dist/manual-ac
RELEASE_EVIDENCE_REPORT="${DELTA_DOCTOR_RELEASE_EVIDENCE_REPORT:-$ROOT_DIR/dist/release-evidence/latest.md}"
GATE_STATUS_FILE="$ROOT_DIR/dist/release-evidence/automated-gate-status"
NOTARY_OUTPUT_DIR="${DELTA_NOTARY_OUTPUT_DIR:-$ROOT_DIR/dist/notarization}"
TIME_MACHINE_SYSTEM_EVIDENCE="${DELTA_DOCTOR_TIME_MACHINE_SYSTEM_EVIDENCE:-$ROOT_DIR/dist/time-machine-system-support/latest.txt}"

blockers=0
warnings=0
Expand Down Expand Up @@ -99,6 +100,7 @@ print_next_actions() {
DELTA_NOTARY_KEYCHAIN_PROFILE="Reccy Notary" Scripts/release.sh finalize
3. Install the notarized app and refresh release evidence:
Scripts/install-app.sh dist/Delta.app
Scripts/run-installed-time-machine-system-support-acceptance.sh /Applications/Delta.app
Scripts/collect-release-evidence.sh dist/Delta.app
4. Complete the manual acceptance matrix for the current commit:
Scripts/create-manual-acceptance-report.sh
Expand Down Expand Up @@ -259,6 +261,19 @@ else
fi

printf "\n## Acceptance Evidence\n\n"
TIME_MACHINE_SYSTEM_DOCTOR_OUTPUT="$(/usr/bin/mktemp -t delta-time-machine-system-evidence-doctor.XXXXXX)"
if [[ -d "$INSTALLED_APP_PATH" ]] \
&& "$ROOT_DIR/Scripts/verify-time-machine-system-support-evidence.sh" \
"$INSTALLED_APP_PATH" \
"$TIME_MACHINE_SYSTEM_EVIDENCE" >"$TIME_MACHINE_SYSTEM_DOCTOR_OUTPUT" 2>&1
then
pass "$(/bin/cat "$TIME_MACHINE_SYSTEM_DOCTOR_OUTPUT")"
else
time_machine_system_output="$(/bin/cat "$TIME_MACHINE_SYSTEM_DOCTOR_OUTPUT" 2>/dev/null || true)"
block "Clean-install authenticated Time Machine system-support evidence is incomplete. ${time_machine_system_output}"
fi
/bin/rm -f "$TIME_MACHINE_SYSTEM_DOCTOR_OUTPUT"

if [[ -f "$RELEASE_EVIDENCE_REPORT" || -L "$RELEASE_EVIDENCE_REPORT" ]]; then
release_commit="$(release_evidence_value "Git Commit")"
release_ready="$(release_evidence_value "Ready for external distribution")"
Expand Down
Loading