rdmsm4x-changelog-20260909-1832-tyrell-build17-notarized-developer-id
2026-09-09 18:32 EDT · rdmsm4x (macOS 27.0, 26A428, Apple Silicon) · Claude Opus 5 (1M context), session_01L5FqUPjVWGsMbP4UhAqAa6
Tyrell 0.2.0 build 17 is the first notarized Tyrell and is now on all six fleet Macs: Developer ID signed, hardened runtime, timestamped, notarized, stapled. Its pinned designated requirement changed for the first time — deliberately — and the installer needed a new, narrowly-scoped authorisation before any host would accept it.
Why it matters
Every Tyrell installed before today was signed
Apple Development, which Apple's notary service does not
accept at all. Gatekeeper's verdict comes from the notarization
ticket, not the certificate, so those builds were
rejected by spctl on every host. Build 17 is
the first that is not. Executing Rich's
DEC-20260909-03.
Scope
All six hosts: rdmsm4x (hub), rdmbair15m5
(designated canary), rdmbair13m5, rdmpw3265m
(Intel), rdmpw3275m (Intel), jdmbair13m5.
Artifact
| Field | Value |
|---|---|
| Release commit | fe32cc3 (hub, backup mirror
and fleet remote all equal) |
| Version / build | 0.2.0 / 17
(was 16) |
| Signer | Developer ID Application: east coast science, llc (ZU2882L4HT),
SHA-1 1C07FCD80C36EAD5D9E3B73F7ACCCA5368DC73FB, login
keychain |
| Runtime / timestamp | flags=0x10000(runtime),
Timestamp=Sep 9, 2026 at 5:53:17 PM |
| Notarization | submission
13bd42f7-fb8d-44c7-848d-b8f59104b093,
Accepted, notarytool log
"issues": null, stapled |
| Candidate sha256 | 8693ebce3d474fed049597d11e3579bf1cd32f7a78acfd9544c78d77592da05e |
| Candidate cdhash (arm64) | 2c5ca2a674febc0fd8cfa011a23b570c0b665b2b75bc68b02774e306f0b49c7a |
| Architecture | lipo -archs =
x86_64 arm64 on Tyrell, tyrellbar
and the embedded LoginItems/TyrellBar.app |
| Entitlements | none, and no
embedded.provisionprofile — no provisioning profile
needed |
| Deployment nonce | build17-20260909-180513 |
The designated requirement moved
| old | 7467a4ae84b198c35fa624c55939276f7ad2989f7dab4e102a71af6f2d7e33dc
(Apple Development chain, ≤ build 16) |
| new | 9943c03ba47653aab0eacfd8fab8ea70d887d4392fd24832b96bffaa9c502aee
(Developer ID chain, build 17 →) |
Measured, not chosen — a candidate built with the new identity was
refused by the existing gate, which printed the value. Re-pinned in
scripts/make_app_bundle.zsh,
scripts/lib/tyrell_app_lifecycle.zsh,
Tests/ScriptTests/tyrell_app_lifecycle_contract_test.zsh,
CLAUDE.md §6 and
docs/ops/2026-09-05-tyrell-release-numbering.md.
ORIGINAL_REQUEST.md keeps the old value as history.
Re-pinning the constant was not enough.
install_app_local.zsh compares the candidate against the
installed bundle, not against the pinned constant, so
with build 16 in place every host would have blocked at exit 3
regardless. Both hashes are now pinned and one named transition is
authorised: --accept-designated-requirement-change permits
prior→current and nothing else — not the reverse, not an unknown hash on
either side, never without the flag. It reports
designated_requirement=migrated_prior_to_current, not
preserved. A build-16 rollback carries the old DR,
so rolling back across this boundary needs the same flag.
Every host's app-scoped TCC grants are re-prompted once as a result. Rich accepted this.
Files changed (repo
~/dev/apps/Tyrell)
scripts/make_app_bundle.zsh→ v1.6: Developer ID pinned by SHA-1, keychain-order precondition,--timestampon every signature, Developer-ID/runtime/timestamp assertions on the artifact, and the notarization gate (notarytool submit --wait→ Accepted → log issues null →stapler staple/validate→spctlsource check → re-verify seal and DR after stapling).--no-notarizefor inner-loop use, refused together with--install.scripts/lib/tyrell_app_lifecycle.zsh→ v1.3: both DR hashes pinned,tyrell_app_designated_requirement_transition_allowed.scripts/install_app_local.zsh→ v2.3:--accept-designated-requirement-change, honestdesignated_requirement=reporting.scripts/redeploy_app_fleet.zsh: forwards the flag; cannot widen what the installer allows.Tests/ScriptTests/make_app_bundle_contract_test.zsh,..._lifecycle_contract_test.zsh: new assertions; thesecuritystub made argument-aware.Tests/ScriptTests/local_path_deps_contract_test.zsh,..._tyrellbar_agent_identity_contract_test.zsh:TEST_ROOTcanonicalised (pre-existing defect, see below).BUILD_NUMBER16→17 and the regeneratedSources/TyrellCore/Version.swiftviascripts/sync_version.zsh.CLAUDE.md§6,docs/ops/2026-09-05-tyrell-release-numbering.md,SESSION-STATE.md,ISSUES.md.
Commands run, in order, with exit codes
| Step | Command | rc |
|---|---|---|
| 1 | make_app_bundle.zsh --no-notarize
on the branch (DR discovery) |
1, printing the new DR — expected |
| 2 | six contract suites on the branch | 0 each; 38 PASS, 0 FAIL |
| 3 | make_app_bundle.zsh from the
hub at fe32cc3 |
0 |
| 4 | redeploy_app_fleet.zsh … --dry-run |
0 |
| 5 | redeploy_app_fleet.zsh … --accept-designated-requirement-change --execute-canary |
0 |
| 6 | run_tyrell_app_canary_probe.zsh --target rdmbair15m5 |
0 (probe_state=passed) |
| 7 | issue_tyrell_app_canary_receipt.zsh
on the canary over pinned ssh |
0
(receipt_state=accepted) |
| 8 | redeploy_app_fleet.zsh … --execute-fleet |
0 (fleet_rollout=complete, 0
pending, 0 failed) |
| 9 | sudo install_app_local.zsh … --accept-designated-requirement-change
(hub) |
0, first attempt |
| 10 | install_service.zsh --server
(hub) |
0 |
| 11 | deploy_fleet.zsh --hosts
×5 |
0 — 5 hosts, all green |
| 12 | launchctl kickstart -k gui/<uid>/com.eastcoastscience.tyrellbar
×6 |
0 each |
Verification evidence — per host, by execution
Read from the artifact by an independent probe, not from any
installer's output. All six identical: CFBundleVersion
17, lipo -archs x86_64 arm64
on the app and the embedded tyrellbar,
Authority=Developer ID Application: east coast science, llc (ZU2882L4HT),
cdhash 2c5ca2a6…, DR 9943c03b…,
stapler validate rc 0,
spctl -a -vv -t exec
source=Notarized Developer ID, one
Tyrell GUI pid executing from /Applications,
tyrellbar from the embedded login item,
tyrelld /api/status 200.
Verified in both directions. The same bundle before
notarization: spctl rc 3
source=Unnotarized Developer ID,
stapler validate rc 65. The installed Build 16:
spctl rc 3
origin=Apple Development: Richard Doty (S65Q255HA8),
stapler validate rc 65 "does not have a ticket stapled to
it".
Two findings
install_app_local.zshnever restarts thetyrellbarLaunchAgent (TyrellISSUES.md#77). On all six hoststyrellbarwas still executing the Build 16 bundle the installer had just moved aside, whileTyrellitself ran correctly from/Applications. Onlylsof -a -p <pid> -d txtshows this —ps -o args=reports the original argv and therefore the correct/Applicationspath, and the agent plist was correct everywhere. Repaired reversibly per host withlaunchctl kickstart -k.jdmbair13m5runs as uid 502, not 501, wheregui/501returns125: Domain does not support specified actionandlaunchctl print gui/501prints nothing — which reads exactly like an unloaded agent and is not one.- Two contract suites were constant-false on every fleet
Mac before this session (#78): they compared
mktempfixtures against results the code resolves with zsh's:A, and/tmpand/var/foldersare symlinks into/private. Measured atbbc8ae7: rc 1 / 0 passed and rc 80 / 0 passed. After canonicalisingTEST_ROOTagainst the same commit: rc 0 / 6 passed and rc 0 / 16 passed. The 17:45 checkpoint records "16/16 new cases" against one of them, so that count cannot have come from a fleet Mac.
Backups / rollback
Nothing deleted. Build 16 is retained on every host
under /Applications/.tyrell-rollbacks/, each promotion
printing rollback_retention_reason=created_this_run; every
non-empty rollback reads CFBundleVersion 16.
| Host | rollback |
|---|---|
| rdmsm4x | 20260909-180820-ea496114ae59
(plus an empty 20260909-133348-… from the 13:33 aborted
attempt) |
| rdmbair15m5 | 20260909-180533-ea496114ae59 |
| rdmpw3275m | 20260909-180632-ea496114ae59 |
| rdmbair13m5 | 20260909-180657-ea496114ae59 |
| jdmbair13m5 | 20260909-180721-ea496114ae59 |
| rdmpw3265m | 20260909-180736-ea496114ae59 |
To undo on a host:
sudo /bin/zsh ~/dev/apps/Tyrell/scripts/install_app_local.zsh --bundle /Applications/.tyrell-rollbacks/<stamp>-ea496114ae59/Tyrell.app --target-user richh --accept-designated-requirement-change
— the flag is required because going back also changes the designated
requirement, and the transition gate authorises only prior→current.
Reverting the fleet wholesale means reverting to tag
v0.2.0-build16's artifact, which is not retained on the
hub; the per-host rollbacks are the supported path.
Note on rdmbair15m5
Unreachable 18:20–18:28:17 EDT. Cause measured on its return: it took
the macOS 27.0 26A428 update — kern.boottime
Wed Sep 9 18:26:10 2026, up 2 mins, load average 776.73.
Not a route fault (it did not answer on the LAN either) and not a deploy
fault: its install was already proven by the canary probe and receipt
that ran on that host after installation, and it was
independently re-verified on return. .local was used for
diagnosis only; every deploy step used the pinned
.ts.dataroo.net name and the canary was never
substituted.
Outstanding owner actions
- TCC re-prompts. Every host's app-scoped Tyrell grants (Full Disk Access, Accessibility, etc.) were reset by the identity change and will be requested again at the console. Rich accepted this in advance; nothing is broken until he answers them.
- Tyrell
ISSUES.md#77 and #78 are open and unassigned. - The remaining apps in
EPIC-20260909-01still ship under Apple Development. The DR-transition finding above is the one to carry across: for any app whose installer pins or compares a designated requirement, re-pinning the constant is not sufficient.
Git
Commits on main: 9aa09f5 (test
canonicalisation), 5a6e056 (Developer ID + DR re-pin +
notarization gate), 4d56a77 (BUILD_NUMBER 17) — merged as
fe32cc3 via PR #24 on the
mirror richhdoty/rdmsm4x-dev-apps-tyrell; state commits
7569905 and 00e282a. Pushed to
backup and fleet. Tag
v0.2.0-build17 at fe32cc3 on
both. CI: script contract tests SUCCESS;
swift build && swift test FAILURE at the known
ECS_LIBS_TOKEN preflight (#67), which fails on every branch
including main.
No secrets are recorded here. The signing identity
is named by its public certificate name and SHA-1 fingerprint; the
notarytool credential is referenced only as keychain profile
ecs-notary.