Fleet changelogs · dev.ecs0.net
rdmsm4x-changelog-20260909-1832-tyrell-build17-notarized-developer-id

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)

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

  1. install_app_local.zsh never restarts the tyrellbar LaunchAgent (Tyrell ISSUES.md #77). On all six hosts tyrellbar was still executing the Build 16 bundle the installer had just moved aside, while Tyrell itself ran correctly from /Applications. Only lsof -a -p <pid> -d txt shows this — ps -o args= reports the original argv and therefore the correct /Applications path, and the agent plist was correct everywhere. Repaired reversibly per host with launchctl kickstart -k. jdmbair13m5 runs as uid 502, not 501, where gui/501 returns 125: Domain does not support specified action and launchctl print gui/501 prints nothing — which reads exactly like an unloaded agent and is not one.
  2. Two contract suites were constant-false on every fleet Mac before this session (#78): they compared mktemp fixtures against results the code resolves with zsh's :A, and /tmp and /var/folders are symlinks into /private. Measured at bbc8ae7: rc 1 / 0 passed and rc 80 / 0 passed. After canonicalising TEST_ROOT against 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

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.