Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ jobs:
./scripts/test-phase1a-wiring.sh
./build/desktidy-sort --phase1a-test
./build/desktidy-sort --phase1a1-test
./build/desktidy-sort --phase1b-test
./build/desktidy-sort --phase3-test
chmod +x scripts/test-phase1a1-public-boundary.sh
./scripts/test-phase1a1-public-boundary.sh
./scripts/build-probe.sh build
Expand Down
12 changes: 7 additions & 5 deletions docs/R1B_PHASE1B_OPERATOR_RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,18 @@ Default is read-only:
DeskTidySacrificialProbe.app/Contents/MacOS/DeskTidySacrificialProbe --plan
```

Mutation (Phase 1B only):
Mutation (Phase 1B only; requires `--commit-mutation` after a sealed grant):

```text
…/DeskTidySacrificialProbe --register --auth-file /path/to/auth.json
…/DeskTidySacrificialProbe --unregister --auth-file /path/to/unreg.json
…/DeskTidySacrificialProbe --register --auth-file /path/to/auth.json --commit-mutation
…/DeskTidySacrificialProbe --unregister --auth-file /path/to/unreg.json --commit-mutation
```

Phase 1A's probe **refuses to invoke** the production mutator even if the
interlock would permit (exit 4). Phase 1B replaces that stop with the single
granted adapter call.
Without `--commit-mutation` the probe still prints `GRANT_PREPARED` and
`STOP_BEFORE_PRODUCTION_ADAPTER` (exit 4). Hosted CI and the public-boundary
suite must not pass that flag with a valid authorization. Separate register
and unregister authorization files/nonces are required.

## Exact readbacks after a real grant

Expand Down
12 changes: 9 additions & 3 deletions docs/R1B_SERVICE_IDENTITY_PROPOSAL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# R1B Service Identity Proposal — Phase 1 input (NOT APPLIED)
# R1B Service Identity Registry

_Phase 0 proposal only. The accepted self set remains `com.desktidy.sort` and
`com.desktidy.notify`. This document does not widen trust._
The accepted production self set remains `com.desktidy.sort` and
`com.desktidy.notify`. The sacrificial SMAppService observation on
2026-08-14 recorded launchd label `com.desktidy.sacrificial` under
parent bundle `com.desktidy.sacrificial-probe`. That label is **not**
a production self-label.

Executable catalog: `src/ServiceIdentity.swift`. ProductIdentity
delegates its accepted self set to that registry.

## Current accepted identity (Phase 0, executable)

Expand Down
54 changes: 54 additions & 0 deletions docs/evidence/R1B_PHASE1B_ABA_EXACTLY_ONCE_GRANT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# R1B Phase 1B A→B→A — exactly-once sealed-grant dispatch

Semantic mutation of `src/GrantedMutation.swift`: drop `O_EXCL` so a
prepared grant can be dispatched twice against the same nonce.

No live ServiceManagement registration. Fake/hermetic roots only.

## A (green)

- file: `src/GrantedMutation.swift`
- SHA-256: `04ab3b4aa252cb16f535d95e136d5ead1b32a01eca2c73ccf277112d25803644`
- command: `xcrun swiftc -O -parse-as-library src/*.swift -o /tmp/desktidy-p1b-aba-36284/desktidy-sort-A && /tmp/desktidy-p1b-aba-36284/desktidy-sort-A --phase1b-test`
- exit: `0`
- excerpt:

```
PASS B11 nonce/grant replay refused on second dispatch
PHASE1B GATES: 25 passed, 0 failed
```

## B (O_EXCL removed)

- SHA-256: `d335d6f5df9251541e9d20d7c96e3fee3737787ee297cfb0f23f43768d04c644`
- rebuild after deleting the previous B binary
- command: `--phase1b-test`
- exit: `1`
- failing IDs:

```
FAIL B11 nonce/grant replay refused on second dispatch — first=invoked(main.SMAdapterStatus.enabled) second=invoked(main.SMAdapterStatus.enabled)
PHASE1B GATES: 24 passed, 1 failed
```

Intended reason: without `O_CREAT|O_EXCL`, the second dispatch of
`nonce-1b11` overwrites the consume-once marker instead of refusing, so
the fake adapter registers twice.

Diff (B vs A):

```diff
- let flags = disableExactlyOnceForMutationTest
- ? (O_CREAT | O_WRONLY)
- : (O_CREAT | O_EXCL | O_WRONLY)
+ let flags = (O_CREAT | O_WRONLY) // B-MUTATION: drop O_EXCL so grant replay can pass
```

The test hook `disableExactlyOnceForMutationTest` is reset to `false` by
`Phase1BTests.runAll()`. Mutating only that default is not load-bearing;
the B mutation therefore changes the open flags themselves.

## Restore

- SHA-256: `04ab3b4aa252cb16f535d95e136d5ead1b32a01eca2c73ccf277112d25803644` (equals A)
- `--phase1b-test` exit 0 — 25/25; B11 PASS
123 changes: 123 additions & 0 deletions docs/evidence/R1B_PHASE1B_SACRIFICIAL_OBSERVATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# R1B Phase 1B — sacrificial SMAppService observation

Date: 2026-08-14
Commit built and observed: `673f49182dad83fd3dc81513927edc2057620abc`
Command: `scripts/observe-phase1b.sh`
Ad-hoc signed probe only. Not Developer ID / not notarized.

Sacrificial root: `/private/tmp/desktidy-phase1b-root-dv5FP4` (outside Desktop, mode 0700, current-user-owned, no foreign overlap).
Probe executable SHA-256: `2f621c2f9979d33782aa89fabd1d4e9abbdf99a19afa1e46e6d86aa2fbb285f6`
Helper SHA-256: `3dfbfb84fd7953cc5b6c4219decf862df251cd20b7bf6e727b09a8b12c20dd7c`
Embedded plist SHA-256: `0636033219e20183401573a8b929913e0ecfa9b3f90fd57a614cf55f2601c2c2`
Register authorization digest: `3dd1e2517f2ca46884393922bec1685d632161b6bbefa32a1bf80e9e00abe889`
Unregister authorization digest: `9fa452ffdcc330e902f4a18726a507ed8f9c1ff707f5313b93873babb1a2eecb`
Authorization file bytes were deleted after the lifecycle.

No `launchctl bootstrap/bootout/kickstart/enable/disable`.
No live Desktop traversal or file creation.
No personal-mover mutation.
No Login Items / FDA / TCC UI click.

## Pre-observation `launchctl print` (read-only)

| Label | rc |
|---|---|
| `com.desktidy.sort` | 113 not loaded |
| `com.desktidy.notify` | 113 not loaded |
| `com.desktidy.sacrificial` | 113 not loaded |
| `com.sicarii.desktop-autosort` | 0 loaded |
| `com.sicarii.desktop-autosort-notify` | 0 loaded |

## Register

```
GRANT_PREPARED
operation=register
executableSHA256=2f621c2f9979d33782aa89fabd1d4e9abbdf99a19afa1e46e6d86aa2fbb285f6
sourceCommit=673f49182dad83fd3dc81513927edc2057620abc
root=/private/tmp/desktidy-phase1b-root-dv5FP4
nonce=nonce-r2-reg1
transactionID=06f88019b9a2038879aa4aae912d13359a289eedac7d4e9a30a45f0b7799eb9f
MUTATION_ATTEMPTED
dispatch_result=invoked
status=enabled
ledger_constructions=1
ledger_registers=1
ledger_unregisters=0
register_exit=0
```

`launchctl print gui/501/com.desktidy.sacrificial` rc=0. Observed fields:

- launchd label: **`com.desktidy.sacrificial`**
- `managed_by = com.apple.xpc.ServiceManagement`
- `path = (submitted by smd.12013)`
- `state = not running`
- `program identifier = Contents/MacOS/SacrificialHelper (mode: 2)`
- `parent bundle identifier = com.desktidy.sacrificial-probe`
- `XPC_SERVICE_NAME => com.desktidy.sacrificial`
- `runs = 0`
- `last exit code = (never exited)`

Helper did not run (`RunAtLoad`/`KeepAlive` false). Heartbeat absent.
Login Items visible string: **not read** (no System Settings UI).
FDA/TCC: **not observed**.
Reboot/login: **not performed**.

## Unregister

```
GRANT_PREPARED
operation=unregister
executableSHA256=2f621c2f9979d33782aa89fabd1d4e9abbdf99a19afa1e46e6d86aa2fbb285f6
sourceCommit=673f49182dad83fd3dc81513927edc2057620abc
root=/private/tmp/desktidy-phase1b-root-dv5FP4
nonce=nonce-r2-unreg1
transactionID=b831b37b90611d368b7cd3813bd663d9ea7f24981f917c8331cd1b3929c9b60b
MUTATION_ATTEMPTED
dispatch_result=invoked
status=notRegistered
ledger_constructions=1
ledger_registers=0
ledger_unregisters=1
unregister_exit=0
```

`launchctl print gui/501/com.desktidy.sacrificial` rc=113 (not loaded).

Rollback elapsed: 0 seconds. Watchdog did not fire.

## Post-observation live print

| Label | rc |
|---|---|
| `com.desktidy.sort` | 113 |
| `com.desktidy.notify` | 113 |
| `com.desktidy.sacrificial` | 113 |
| `com.sicarii.desktop-autosort` | 0 |
| `com.sicarii.desktop-autosort-notify` | 0 |

Independent post-run reread: production and sacrificial absent; both personal labels still loaded.

## Adapter / transaction ledger (redacted)

Precall:

```
06f88019b9a2038879aa4aae912d13359a289eedac7d4e9a30a45f0b7799eb9f nonce-r2-reg1 register <exe> <commit> 3dd1e2517f2ca46884393922bec1685d632161b6bbefa32a1bf80e9e00abe889
b831b37b90611d368b7cd3813bd663d9ea7f24981f917c8331cd1b3929c9b60b nonce-r2-unreg1 unregister <exe> <commit> 9fa452ffdcc330e902f4a18726a507ed8f9c1ff707f5313b93873babb1a2eecb
```

Postcall:

```
06f88019… nonce-r2-reg1 register invoked enabled
b831b37b… nonce-r2-unreg1 unregister invoked notRegistered
```

## What this does not authorize

Do **not** add `com.desktidy.sacrificial` to `ProductIdentity.selfLabels`
from this transcript. This is one sacrificial observation on this Mac. It
does not prove reboot/login, Login Items pixels, FDA/TCC, or production
Desktop migration. A future production app-agent label remains unobserved.
23 changes: 23 additions & 0 deletions docs/evidence/phase1b-observation/codesign-probe.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Executable=/private/tmp/desktidy-phase1b-build-NRNlGf/DeskTidySacrificialProbe.app/Contents/MacOS/DeskTidySacrificialProbe
Identifier=com.desktidy.sacrificial-probe
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20400 size=1047 flags=0x2(adhoc) hashes=26+3 location=embedded
VersionPlatform=1
VersionMin=917504
VersionSDK=1705216
Hash type=sha256 size=32
CandidateCDHash sha256=fdab8a64dc663d69bf3febdd004105c4058d1742
CandidateCDHashFull sha256=fdab8a64dc663d69bf3febdd004105c4058d17420a591fb7e4b55f782270f220
Hash choices=sha256
CMSDigest=fdab8a64dc663d69bf3febdd004105c4058d17420a591fb7e4b55f782270f220
CMSDigestType=2
Executable Segment base=0
Executable Segment limit=196608
Executable Segment flags=0x1
Page size=16384
CDHash=fdab8a64dc663d69bf3febdd004105c4058d1742
Signature=adhoc
Info.plist entries=8
TeamIdentifier=not set
Sealed Resources version=2 rules=13 files=2
Internal requirements count=0 size=12
1 change: 1 addition & 0 deletions docs/evidence/phase1b-observation/observed-label.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.desktidy.sacrificial
2 changes: 2 additions & 0 deletions docs/evidence/phase1b-observation/postcall.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
06f88019b9a2038879aa4aae912d13359a289eedac7d4e9a30a45f0b7799eb9f nonce-r2-reg1 register invoked enabled 2026-08-14T16:54:44Z
b831b37b90611d368b7cd3813bd663d9ea7f24981f917c8331cd1b3929c9b60b nonce-r2-unreg1 unregister invoked notRegistered 2026-08-14T16:54:44Z
2 changes: 2 additions & 0 deletions docs/evidence/phase1b-observation/precall.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
06f88019b9a2038879aa4aae912d13359a289eedac7d4e9a30a45f0b7799eb9f nonce-r2-reg1 register 2f621c2f9979d33782aa89fabd1d4e9abbdf99a19afa1e46e6d86aa2fbb285f6 673f49182dad83fd3dc81513927edc2057620abc 3dd1e2517f2ca46884393922bec1685d632161b6bbefa32a1bf80e9e00abe889 2026-08-14T16:54:44Z
b831b37b90611d368b7cd3813bd663d9ea7f24981f917c8331cd1b3929c9b60b nonce-r2-unreg1 unregister 2f621c2f9979d33782aa89fabd1d4e9abbdf99a19afa1e46e6d86aa2fbb285f6 673f49182dad83fd3dc81513927edc2057620abc 9fa452ffdcc330e902f4a18726a507ed8f9c1ff707f5313b93873babb1a2eecb 2026-08-14T16:54:44Z
14 changes: 14 additions & 0 deletions docs/evidence/phase1b-observation/register.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
GRANT_PREPARED
operation=register
executableSHA256=2f621c2f9979d33782aa89fabd1d4e9abbdf99a19afa1e46e6d86aa2fbb285f6
sourceCommit=673f49182dad83fd3dc81513927edc2057620abc
root=/private/tmp/desktidy-phase1b-root-dv5FP4
nonce=nonce-r2-reg1
transactionID=06f88019b9a2038879aa4aae912d13359a289eedac7d4e9a30a45f0b7799eb9f
MUTATION_ATTEMPTED
dispatch_result=invoked
status=enabled
ledger_constructions=1
ledger_registers=1
ledger_unregisters=0
register_exit=0
14 changes: 14 additions & 0 deletions docs/evidence/phase1b-observation/unregister.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
GRANT_PREPARED
operation=unregister
executableSHA256=2f621c2f9979d33782aa89fabd1d4e9abbdf99a19afa1e46e6d86aa2fbb285f6
sourceCommit=673f49182dad83fd3dc81513927edc2057620abc
root=/private/tmp/desktidy-phase1b-root-dv5FP4
nonce=nonce-r2-unreg1
transactionID=b831b37b90611d368b7cd3813bd663d9ea7f24981f917c8331cd1b3929c9b60b
MUTATION_ATTEMPTED
dispatch_result=invoked
status=notRegistered
ledger_constructions=1
ledger_registers=0
ledger_unregisters=1
unregister_exit=0
53 changes: 45 additions & 8 deletions probe/ProbeMain.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import Foundation

// Sacrificial operator probe. Default is read-only plan/status.
// Phase 1A.1: measure evidence, prepare a sealed grant, then STOP.
// Does not construct ProductionSMAdapter or invoke mutation methods.
// A prepared grant still STOPs unless --commit-mutation is present.
// Hosted CI and the public-boundary suite must not pass that flag
// with a valid authorization.
@main
struct SacrificialProbe {
static func main() {
Expand Down Expand Up @@ -76,10 +77,46 @@ struct SacrificialProbe {
print("sourceCommit=\(grant.sourceCommit)")
print("root=\(grant.rootCanonical)")
print("nonce=\(grant.nonce)")
print("STOP_BEFORE_PRODUCTION_ADAPTER")
print("transactionID=\(SacrificialMutationDispatcher.transactionID(for: grant))")
if !args.contains("--commit-mutation") {
print("STOP_BEFORE_PRODUCTION_ADAPTER")
print("ledger_constructions=\(ProductionMutationLedger.constructions)")
print("ledger_registers=\(ProductionMutationLedger.registerInvocations)")
exit(4)
}
let adapter = ProductionSMAdapter()
let req = SacrificialDispatchRequest(
grant: grant,
requested: grant.operation,
plistName: SacrificialIdentity.hypothesizedPlistName,
liveIdentity: identity,
compiledSourceCommit: CompiledProbeIdentity.sourceCommit,
second: a2
)
let outcome = SacrificialMutationDispatcher.dispatch(req, adapter: adapter)
print("MUTATION_ATTEMPTED")
switch outcome {
case .invoked(let st):
print("dispatch_result=invoked")
print("status=\(st)")
case .refused(let r):
print("dispatch_result=refused")
print("dispatch_error=\(r)")
case .rollbackRequired(let r):
print("dispatch_result=rollbackRequired")
print("dispatch_error=\(r)")
case .indeterminate(let r):
print("dispatch_result=indeterminate")
print("dispatch_error=\(r)")
}
print("ledger_constructions=\(ProductionMutationLedger.constructions)")
print("ledger_registers=\(ProductionMutationLedger.registerInvocations)")
exit(4)
print("ledger_unregisters=\(ProductionMutationLedger.unregisterInvocations)")
switch outcome {
case .invoked: exit(0)
case .indeterminate, .rollbackRequired: exit(5)
case .refused: exit(3)
}
}
}
}
Expand All @@ -89,10 +126,10 @@ struct SacrificialProbe {
static func planText() -> String {
"""
DeskTidy sacrificial SMAppService probe (NON-PRODUCTION)
Phase 1A.1 seals measurement and grant preparation only.
Default: read-only plan. No registration in Phase 1A.1.
A future Phase 1B requires a reviewed patch connecting the sealed
grant to exactly one adapter call, plus separate architect authorization.
Default: measure, prepare grant, STOP (exit 4). No adapter construction.
Phase 1B observation requires --commit-mutation after a sealed grant.
Hosted CI and the public-boundary suite must not pass that flag
with a valid authorization.
Hypothesized plist name: \(SacrificialIdentity.hypothesizedPlistName)
Hypothesized label: \(SacrificialIdentity.hypothesizedLabel) (UNOBSERVED)
Bundle id: \(SacrificialIdentity.bundleID)
Expand Down
Loading
Loading