Release facts authority:
.github/release-manifest.json(schemadocs/schemas/release-manifest.schema.json). Published:v1.4.5(version1.4.5, build13),arm64only, minimum macOS15.0. Runtime identities: apptech.reidar.vifty, daemontech.reidar.vifty.daemon, helpertech.reidar.vifty.helper, CLItech.reidar.vifty.ctl. Canonical artifact:Vifty-v1.4.5.zipwith checksum assetVifty-v1.4.5.zip.sha256and SHA-25613fa763cbfdca3e77fcf6f657df6d51b32e19a4d25dd17a79614635fe844b0d5. Public artifact trust:passed/developer-id-notarizedfor TeamIDX88J3853S2; source174dcd28a343de7f797d682d02c0f70e26b72c2e, CI run31283125895, Release run31284620552. Tag policy:v1.4.5remains recorded assigned-verifiedevidence; signed tags are mandatory from version1.3.3onward. Separate exact-build claims: installed release reviewpending; manual Fixed/Curve/Auto compatibilitypending.
Vifty has two release modes:
- Source-first release: used when Developer ID credentials are unavailable. The GitHub Release is the source tag plus release notes, with an optional clearly marked unsigned tester app zip.
- Developer ID release: trusted binary lane. Artifacts must be Developer ID signed, notarized, stapled, and tied to the same TeamID that the privileged daemon enforces over XPC.
For the current public release trust state, see release-status.md. Keep that page updated when a release workflow fails, succeeds, or when the cask checksum is updated.
Release-availability checking, manual public-archive installation, and future in-place updating are three separate trust lanes. Source-first, unsigned-dev, local ad-hoc, and CI builds must remain ineligible and make zero update requests. The checker is present in the current v1.4.5 artifact. The manual bridge takes only an operator-supplied archive selected as the reviewed checkout's current publishedRelease, performs no download, verifies the manifest-pinned SHA plus Developer ID/notarization evidence, and enters the existing fail-closed replacement transaction. Do not enable Sparkle for those artifacts; the checker, manual bridge, and future installer requirements live in auto-update.md.
After publication, manifest promotion, and review of the exact public archive, an operator can install that current published release without rebuilding it:
scripts/install-vifty.sh --public-release-archive /absolute/path/Vifty-vX.Y.Z.zip
# or
make install-public-release PUBLIC_RELEASE_ARCHIVE=/absolute/path/Vifty-vX.Y.Z.zipUse a reviewed checkout whose .github/release-manifest.json has already promoted the intended version to publishedRelease. The bridge starts at v1.4.0, when the public bundle includes the root snapshot binding contract. The command refuses a candidate, historical entry, direct .app, URL, relative path, noncanonical name, or SHA override. It first requires the exact pinned archive checksum and verified signed tag/signer-policy continuity, then performs bounded private extraction and complete content binding. A no-skip verifier result plus independent extracted-bundle checks must then pass version/build, bundle identities, Developer ID TeamID, deep signing, notarization/stapling, and Gatekeeper before installation begins. Installation uses a private per-destination lock and the same Auto/System replacement preflight, authority freeze, exact root snapshot, post-swap verification, and rollback transaction as the source-build lane; it refuses authenticated downgrades and never retries at a second destination after replacement starts. This command does not fetch GitHub, is not invoked by Update to latest version, and is not a Sparkle or automatic-update path.
Use this mode when Apple Developer Program credentials are unavailable. It does not create a trusted public binary and must not claim Developer ID signing, notarization, stapling, Gatekeeper approval, or Homebrew trust.
The required source-first release-note warning is:
This is a source-first release. Vifty v does not yet include a Developer ID signed or notarized public binary because the project does not currently have Apple Developer Program credentials.
A convenience unsigned
.appbuild is attached for testers who understand macOS Gatekeeper warnings and prefer not to build locally. For the most trusted path, build from source.
Source-first checklist:
-
Run the local verification suite:
make verify-full
-
Generate source-first release notes:
RELEASE_VERSION=<version> make source-first-release-notes
The generated notes include a Source Provenance section with the source ref and immutable commit SHA. The Makefile target resolves
v<version>by default, so fetch tags first if needed. For an unpublished candidate, intentionally override the ref:SOURCE_FIRST_SOURCE_REF=<candidate-ref-or-sha> RELEASE_VERSION=<version> make source-first-release-notes
Keep any later
maincommits described as post-release hardening until a future release is cut. -
Optionally build the unsigned tester artifact:
git fetch origin main --tags git checkout v<version> make unsigned-dev-artifact
The only acceptable unsigned tester names are:
Vifty-v<version>-unsigned-dev.zipVifty-v<version>-unsigned-dev.zip.sha256
The unsigned-dev zip is valid only with its
.sha256sidecar, and the SHA-256 digest in that sidecar must match the zip bytes.The Makefile target requires the current source to match
v<version>by default. If you are building from an unpublished candidate, run the script directly with--require-source-ref <candidate-ref-or-sha>or setUNSIGNED_DEV_SOURCE_REF=<candidate-ref-or-sha>intentionally. Do not build aVifty-v<version>-unsigned-dev.zipfrom latermainhardening, and do not useVifty-v<version>.ziporVifty-v<version>.zip.sha256for an unsigned build. Those names are reserved for Developer ID signed and notarized artifacts. -
Before pushing the source tag, optionally confirm that the tag or candidate commit matches the intended source ref:
git fetch origin main --tags scripts/check-release-readiness.sh \ --mode source-first \ --version <version> \ --repo Reedtrullz/Vifty \ --require-source-ref <candidate-ref-or-sha> \ --json
Use a moving branch such as
origin/mainonly while it is intentionally the release candidate. After publication,mainmay move on; do not compare an already-published source-first tag toorigin/mainunless that is still the intended release commit. -
Push the source tag and create or update the GitHub Release with the source-first notes, including the generated immutable source provenance. The unsigned-dev zip/checksum are optional tester convenience assets only; if attached, the
.sha256digest must match the zip bytes. -
Confirm published source-first readiness:
git fetch origin main --tags RELEASE_VERSION=<version> make source-first-readiness
If you need an immutable source-ref check after publication, use the release commit SHA rather than a moving branch.
-
Keep
Casks/vifty.rbdisabled for the source-first release. Do not update its checksum, re-enable it, or point the cask at the unsigned-dev artifact.
Use this mode only after the intended personal Apple Developer team is active and Vifty is ready to cross from source-first distribution into trusted-binary release claims.
Use only the Apple Developer team intentionally designated to own Vifty's public signing identity, GitHub Release trust, Homebrew trust, support burden, and revocation risk. Do not borrow a different organization's Developer ID certificate for a release.
Keep the signing boundary reproducible and private:
- Keep the Developer ID workflow strict and require artifact verification before publication.
- Configure the GitHub
releaseenvironment for Vifty's explicit solo-maintainer policy: no required-reviewer rule, administrator bypass disabled,protected_branches: false,custom_branch_policies: true, no branch policy, and exactly one custom deployment policy with typetagand patternv*. This admits the immutable signed-tag workflow ref while rejecting branch-based deployment. A YAMLenvironment: releasedeclaration alone is not proof of protection because GitHub can create a referenced missing environment with permissive defaults. There is no eligible second human release reviewer in the current policy: zero required approvals is an explicit solo-maintainer constraint, not independent review evidence, and release records must not invent a reviewer or label the absence of a reviewer as approval. After release prep is merged and exact-commit CI passes, and immediately before creating the release tag, an authenticated repository administrator runsscripts/create-signed-release-tag.sh. The tag creator invokes the exact committedscripts/check-release-governance.shitself to bind that protected-main SHA to the complete environment, branch, secret-scope, tag-absence, and no-bypass tag-ruleset contract; it embeds those exact live bytes, verifies signature and current freshness, then repeats the full live readback after signing. It never accepts caller-authored evidence. The trustedsign-notarizejob separately checks out the exact signed-taggithub.sha, validates the embedded evidence with the committedscripts/validate-release-governance-evidence.rb, consumes the current-fresh admission record persisted by the read-only prepare job, and repeats only the governance facts available through GitHub's public API before any step consumes a signing secret. GitHub may resolve repository secrets when the workflow is queued; this ordering constrains use, not GitHub's internal read time. A future move to peer approval must be a reviewed policy change rather than a dormant or misleading reviewer count. - Keep Sparkle installation keys out of
Resources/Info.plistuntil a separately verified signed-appcast installer release is ready. The advisory GitHub release checker does not require or implySUFeedURL,SUPublicEDKey, an appcast, executable download, or in-place installation. - Store certificate material,
.p12passwords, Apple app-specific passwords, and other secret values only in the local keychain or the repository's GitHub Actions secrets; never commit them or record them in project notes. Vifty deliberately retains its existing six repository-scoped release secrets for the solo-maintainer workflow. Do not add same-name environment copies: GitHub would give those values precedence inside the environment-bound job. The operator preflight requires all six repository names and rejects every matching environment name. The workflow contract allowlists the exact six${{ secrets.NAME }}references and permits them only on the protectedsign-notarizejob after the non-secret validation steps. - Run the local signing/notarization smoke test and the release-secret name preflight before pushing a public tag.
Configure these six GitHub Actions secrets at repository scope before running the Release workflow. The name-only operator preflight calls both gh secret list --repo ... and gh secret list --env release --repo ...; it never reads values. It requires all repository names and rejects any same-name environment secret. Environment protection and secret storage are separate controls here: the environment admits only a custom tag: v* deployment policy with administrator bypass disabled, while the checked-in workflow contract confines all secret references to the exact protected signing job.
APPLE_TEAM_ID— Apple Developer TeamID used for signing and XPC allowlisting.APPLE_ID— Apple ID email forxcrun notarytool.APPLE_APP_SPECIFIC_PASSWORD— app-specific password for notarization.DEVELOPER_ID_APPLICATION_IDENTITY— exact codesign identity, for exampleDeveloper ID Application: Example, Inc. (TEAMID).DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64— base64-encoded.p12Developer ID Application certificate.DEVELOPER_ID_APPLICATION_CERTIFICATE_PASSWORD— password for the.p12.
Before tagging a Developer ID release, add a non-null candidate to .github/release-manifest.json and align the candidate identity without repointing Homebrew:
.github/release-manifest.jsoncandidateversion,build, tag, and canonical asset namesResources/Info.plistCFBundleShortVersionStringResources/Info.plistmonotonically increasingCFBundleVersion- Git tag, formatted as
v<version>
The candidate build must be greater than the published build. Casks/vifty.rb must remain exactly on the manifest's published version and SHA until the new notarized artifact exists, passes verification, and the candidate is promoted; pointing the cask at an unpublished candidate would create a broken install URL/checksum. The release workflow fails if the tag-derived version, candidate, bundle version/build, published cask version/SHA, release artifact names, cask signing metadata, safe bundled uninstall lifecycle, TeamID-gated daemon build configuration, notarization/stapling gates, verifier signature/notarization checks, Gatekeeper assessment, or GitHub Release asset publication does not align. A candidate checksum may remain null until the notarized zip exists; only the protected pre-publication verifier may use a differing explicit --expected-sha from the just-created sidecar. Current and historical published entries are always verified against their immutable manifest SHA, and every selected entry—including a candidate—must resolve releaseSourceCommit from the exact manifest tag's peeled commit. The Homebrew cask remains bound only to the current publishedRelease.
historicalReleases is append-only. CI materializes the prior manifest and its continuity checker from the trusted PR base, push-before commit, or exact first parent. Existing historical entries must remain an unchanged prefix; when publishedRelease changes, the previous published object must be appended unchanged exactly once. A missing prior manifest is accepted only for the pinned initial v1.3.2 manifest introduction.
In a version-2 verifier summary, releaseVersion, releaseTag, releaseSourceCommit, releaseManifestEntryKind, and releaseManifestSHA256 bind the evidence to the exact .github/release-manifest.json bytes stored at the peeled release-tag commit. Promotion does not rewrite that immutable identity: a summary created from a tagged candidate remains a candidate-snapshot summary after the current manifest moves the release to publishedRelease and later historicalReleases. Evidence collection/review separately requires the current authoritative manifest to preserve the tagged version/build/tag/artifact asset names, bind sourceCommit to the peeled tag commit, and pin the summary's actual artifact SHA. Passed summaries contain the exact unique 14-check verifier set, with every check marked passed in release-trust scope. The protected release workflow may temporarily verify a current candidate with a null manifest SHA only because the protected publication contract independently carries that just-built SHA into the write-scoped publish job. Legacy schema-version-1 evidence acceptance is limited to the exact public v1.3.2 artifact and its historical check set; a separate verifier-only v1.3.2 fallback exists because that tag predates the manifest file, but its generated v2 result is not accepted by strict evidence collection/review. Later releases cannot use either escape hatch. The workflow must not pass verifier skip flags that disable public signature or notarization checks. The cask must not declare signing_identity identity: "-" or bypass the bundled lifecycle with direct sudo launchctl/rm teardown.
The candidate notarized release asset is named Vifty-v<version>.zip; Casks/vifty.rb points at that name only after candidate promotion and checksum handoff.
-
Confirm the release secrets are configured:
scripts/check-release-secrets.sh --repo Reedtrullz/Vifty
This checks only secret names, never values. It should report all required repository-scoped Developer ID and notarization names and confirm that the
releaseenvironment contains no same-name shadow before a release tag is pushed. A Release workflow run is never rerun; if an original transaction conclusively fails without publication and a new patch is prepared, repeat this preflight for that new tag. An unreadable scope, missing repository name, or matching environment name blocks the operator preflight. -
Update
CHANGELOG.md: moveUnreleasedentries under the new version and date. Add the release candidate to.github/release-manifest.json, choose a build number greater than the published build, and updateResources/Info.plistto the candidate version/build. Do not edit, delete, or reorder priorhistoricalReleases. During a later promotion, append the exact previouspublishedReleasebefore replacing it. KeepCasks/vifty.rbexactly on the current published manifest version/SHA throughout candidate build, signing, notarization, and publication. The candidate SHA may remainnulluntil the notarized artifact exists. Do not repoint or recommend Homebrew until the candidate is promoted and the public checksum has been applied and verified. -
Run the exact local release gate on the prepared candidate:
RELEASE_METADATA_MODE=developer-id make verify-full
-
Commit the release prep, merge it to protected
main, and wait for CI to pass on that exact merged commit. The cask checksum and published manifest facts are post-publication follow-up changes. -
Immediately after that exact-commit CI pass, move to a clean checkout of that exact protected-main commit and let the tag creator acquire the complete administrator-visible governance boundary itself:
git fetch origin main --tags EXACT_MAIN_SHA="$(git rev-parse origin/main)" git switch --detach "$EXACT_MAIN_SHA" test "$(git rev-parse HEAD)" = "$EXACT_MAIN_SHA" test -z "$(git status --porcelain=v1 --untracked-files=all)"
Before collecting governance evidence, the tag creator materializes the exact commit, runs that snapshot's manifest-history checker against the exact first parent, validates that snapshot's workflow contract and Info.plist version, and requires a successful completed
pushCI run whose branch ismainand whoseheadShais the exact release commit. It requires the complete reviewed release-tool set—including both workflows, governance/provenance/inventory/signing/verification scripts, the signer allowlist, and the pinnedghpolicy/verifier—to be byte-identical to the exact first parent, so a release-prep commit cannot silently revise its own release authority.The internally invoked administrator-authenticated read-only gate must show
apiHost: "github.com",dataSource: "github-api-live",evidenceScope: "administrator-pretag"; the exact authenticated actor ID/login; the exact expectedmainSHA; proof that the tag is still absent;releaseGovernanceMode: "solo-maintainer"; no environment reviewer gate;administratorsCanBypass: false;protected_branches: false;custom_branch_policies: true; no branch policy; exactly one custom deployment policy with typetagand patternv*; a required pull request with zero approvals and no bypass actors; strict Actions-ownedSwiftPM checksenforced for administrators; conversation resolution; no force-push or deletion allowance; all six repository secret names with no environment shadow; exactly one active tag ruleset whose conditions are exactlyinclude: [refs/tags/v*]andexclude: [], whose visible bypass list is empty, whoserulesetUpdatedAtis a canonical UTC revision no later than the observation, whosecurrentUserCanBypassisnever, and whose rules are exactlyupdateplusdeletion; and the SHA-256 digests of the governance checker, environment checker, secret checker,scripts/verify-release-gh-toolchain.rb, and.github/release-gh-toolchain.json. Any missing, unreadable, ambiguous, stale, or drifting fact blocks tag creation.The workflow cannot truthfully re-prove administrator-only branch settings or the complete tag-ruleset bypass-actor list with its built-in token. It records
privilegedSettingsVerified: falseandbypassActorsVerified: false, while independently requiring the public environment policy, exact protected-main SHA, Actions-owned required check, the same ruleset ID/ref, exactupdated_atrevision,current_user_can_bypass: "never"for the workflow caller, andupdateplusdeletionrules. It rechecks that exact public revision and caller-bypass state immediately before and after promotion. The signed administrator-pretag evidence proves the privileged empty-bypass-list facts at observation time; the workflow's public readbacks prove only the facts its token can observe during the run. -
Create the signed annotated tag, then let the hardened helper push that exact object and observe the automatically triggered workflow:
scripts/create-signed-release-tag.sh \ --tag v<version> \ --commit "$EXACT_MAIN_SHA" \ --evidence-output .build/release-governance-administrator-pretag.json scripts/push-and-dispatch-signed-release-tag.sh \ --tag v<version> \ --commit "$EXACT_MAIN_SHA"
scripts/create-signed-release-tag.shrequires a clean checkout at the exact commit, complete first-parent release-tool/signer/toolchain continuity, the exact committed manifest/workflow gates, and an exact-main successful push-CI run before it invokes the exact committed governance checker against github.com. It verifies the Apple or 1Password signing program's exact code-signing requirement, removes GitHub credentials from the signer's environment, embeds the same-actor live evidence bytes in the annotated tag, signs it, verifies the exact annotated-tag object against the continuous signer policy, enforces freshness again after the interactive signing prompt, and repeats the full live governance/tag-absence/main/ruleset ID/revision/current-caller-bypass check before reporting success. It never accepts caller-authored evidence and never pushes.scripts/verify-release-gh-toolchain.rbcopies and verifies the reviewed Darwin arm64ghbytes before token access, and both verifier/policy hashes are bound into the signed governance evidence. Despite its retained filename,scripts/push-and-dispatch-signed-release-tag.shnever callsworkflow_dispatch: it repeats the clean-tree, signature, first-parent continuity, exact-main CI, current signed-evidence, same-actor live governance, remote-main/tag/branch checks, and an authenticated paginated scan proving that no draft or published release owns the tag; pushes only the captured annotated-tag object to the literal github.com tag ref with an absent-ref compare-and-swap lease; and reads back the exact remote tag object and peeled commit. That tag push automatically startsRelease <tag>. The helper resolves the numeric Release workflow ID only to correlate and verify exactly onepush-event run at attempt 1, including its actor ID/login, repository, workflow path/ID, tag, commit, URL, and creation time. Immediately before the push boundary it atomically creates~/Library/Application Support/Vifty/ReleaseTransactions/Reedtrullz-Vifty/<tag>/retired.json; it keeps the privatereceipt.jsonbeside that checkout-independent marker. The marker and receipt are inspection evidence only and never authorize a retry. Do not replace this helper with raw push commands, manual workflow dispatch, or a workflow rerun.Remote proof stops at the GitHub boundary. The workflow can verify the signed annotated tag, embedded governance evidence, actor, ref, commit, push event, and run attempt, but GitHub cannot attest that the local helper ran or that
retired.jsonorreceipt.jsonexists. The sole signer/repository administrator is therefore an explicit operational trust root not to bypass the supported helper with a raw tag push or out-of-band GitHub Release create, edit, or delete. Such a path is unsupported and lacks the local one-shot transaction guarantee even if every remote admission check passes; it never authorizes manual dispatch or rerun.Treat the helper as a one-shot transaction. A failure conclusively proven to precede both creation of the durable retired-tag marker and any remote mutation may be retried only after reacquiring fresh gates and proving the exact remote tag ref is still absent. Once that marker exists or the push began—including an unknown push result, duplicate or concurrent creation, accepted-but-unconfirmed push, post-tag governance failure, missing/ambiguous run observation, or mismatched run verification—do not invoke the helper a second time, dispatch manually, rerun the workflow, or delete, move, or reuse the tag. Inspect the original
retired.json,receipt.json, immutable tag, first-attempt workflow run, and release state; while the outcome is inconclusive, keep inspecting rather than starting a replacement transaction. If the original transaction is conclusively shown not to have published, cut a new patch version.Future signed-tag policy starts at the manifest's non-null
signedTagsRequiredFromVersion; the historical unsignedv1.3.2record remains evidence and is not rewritten. Release publication is triggered only by pushing the exact signedv*tag. The first and only permitted workflow attempt must admit that tag within the 15-minute live-evidence window and persist the current-fresh admission in the hashed candidate handoff, whose canonical complete-tree inventory also binds the archive and admission bytes. A missed admission window, rejected first attempt, or other failure is never repaired by manual dispatch or rerun; once non-publication is conclusive, prepare a new patch version rather than reusing the immutable tag. Later signing, notarization, and publication steps validate the persisted admission record and current live facts without extending that initial 15-minute window across the entire release. The workflow revision must be the exact signed-tag commit. It extracts the embedded evidence and runs the committedscripts/validate-release-governance-evidence.rb, which binds the observation start/end, tag name, tagger time, protected-main/tag commit, committed checker/dependency SHAs, ruleset revision/current-caller-bypass state, and initial admission freshness. The verified annotated-tag object SHA, peeled commit SHA, governance-evidence digest, signed ruleset ID/revision, and admission digest are carried in the publication contract and rechecked around promotion. -
Watch the automatically triggered first-attempt
Release <tag>workflow. It will:- validate the manifest candidate, exact-first-parent signer-policy continuity, exact tag-object signature, and bundle version/build;
- extract the exact administrator-pretag evidence embedded in the signed annotated tag and validate its committed checker SHA, freshness, tag/tagger/main identity, and privileged governance contract;
- verify remote tag-object parity, protected-main ancestry, manifest history against the exact first parent, and a successful completed main-push CI run for the exact tag commit;
- validate candidate bundle identity while proving the cask still names the exact published manifest artifact and SHA;
- run pinned, checksum-verified
actionlint, the full XCTest suite, warnings-as-errors build, and ad-hoc app assembly in the read-only prepare job; - create a canonical complete candidate inventory covering every directory, regular file content/size/mode, and relative in-bundle symlink target, plus the normalized archive size/digest and current-fresh admission-provenance size/digest; round-trip that archive and upload exactly the archive, admission, and inventory with no extra handoff entries;
- enter the protected GitHub
releaseenvironment and complete the non-secret governance and provenance steps before any step consumes a signing secret; - check out the exact trusted signed-tag workflow revision separately, inventory its workflows, workflow-contract checker, release scripts, schemas, manifest, cask, and entitlements, validate that exact workflow contract, then revalidate the trusted inventory around protected execution;
- download the exact three-file handoff, reject missing or extra entries, verify archive/admission bytes before and after extraction, safely extract only
Vifty.app, and require the complete extracted tree to equal the recorded inventory without executing release scripts supplied by the candidate tag; - import the Developer ID Application certificate with private file modes into a temporary keychain, bind
codesignto that keychain, and sign the already-built candidate without SwiftPM, tests, or a generalmaketarget; - verify all signing identifiers, TeamID, bundle/daemon identities, build number, exact architectures, hardened runtime flags, and exact manifest-authorized app entitlements;
- submit the app to Apple notarization;
- staple the notarization ticket;
- delete the temporary keychain and certificate in an
always()cleanup step; - zip
Vifty.appasVifty-v<version>.zip; - verify the generated zip artifact against its just-created checksum, signing TeamID, hardened runtime/entitlements, stapling, Gatekeeper state, and bundled schemas exactly matching the manifest-authorized source contracts;
- write
Vifty-v<version>-artifact-summary.json; - write
Vifty-v<version>-release-checklist.md; - pass only the verified assets to a separate publication job, the only job with
contents: write; that job rechecks the full v2 identity/build/architecture/check-array contract, checklist structure, and contract-bound public tag ruleset ID, revision, current-caller no-bypass state, ref coverage, and update/deletion rules before publishing the zip, SHA-256 checksum, verification summary, and release checklist; - generate a cryptographically random run-unique ownership nonce, place its exact marker in the draft body and nonce in the draft title, REST-create the draft, and capture its immutable GitHub release database ID directly from that mutation response;
- upload every canonical asset through the GitHub uploads API path containing that captured database ID; bind each upload response's immutable asset ID, name, byte size,
uploadedstate, andsha256:digest, and require the exact returned release body and complete asset records in every pre/post-promotion GET-by-ID readback; tag-addressed create, edit, upload, and delete commands are forbidden; - refuse promotion unless the exact tag object/commit and the public update/deletion ruleset ID and
updated_atrevision still match the signed governance evidence and the workflow caller still reportscurrent_user_can_bypass: "never", then use only the captured database ID for promotion or containment mutations; - query the release post-state after every create, upload, promote, or re-draft outcome; a nonzero or ambiguous mutation, identity mismatch, signal, or failed final readback arms an ID-based re-draft, and the job hard-fails if a subsequent immutable-ID readback cannot prove
draft: trueplus the exact ownership marker; - if REST creation returns an ambiguous response before an ID is trusted, discover by tag only to locate a unique draft whose numeric ID, exact tag, draft title/state, empty initial asset set, and cryptographically random marker all match this run; an unrelated by-tag release is never patched, even when that makes containment a hard failure;
- prepend the release checklist to generated GitHub Release notes.
Published-artifact verification is version-pinned, not partially pinned: schemas, executable support-script destinations, the complete bundled workload-wrapper set, and app entitlements are derived from the published manifest entry's exact
sourceCommit. Candidate verification deliberately uses the current checkout policy. CI therefore checks out full Git history (fetch-depth: 0); if a published source commit or any required historical contract file is unavailable or invalid, verification fails closed rather than applying futuremainpolicy to old immutable bytes. -
After the exact public artifact and sidecar checksum exist and pass verification, append the complete existing
publishedReleaseobject unchanged to the end ofhistoricalReleases; never edit, delete, or reorder earlier history entries. Then replacepublishedReleasewith the exact candidate facts: version/build/tag, source commit, exact source-CI and Release run IDs, four canonical asset names, SHA-256, and trust states; finally clearcandidate. The appended history entry must remain fact-for-fact identical to the prior current entry, and the new current version/build must be greater than every historical entry. Do not promote the manifest before those public facts exist. Apply the same authorized SHA-256 toCasks/vifty.rb:git switch -c codex/v<version>-release-facts RELEASE_ASSET_DIR="$PWD/.build/release-assets/v<version>" rm -rf "$RELEASE_ASSET_DIR" mkdir -p "$RELEASE_ASSET_DIR" gh release download v<version> --repo Reedtrullz/Vifty \ --dir "$RELEASE_ASSET_DIR" \ --pattern 'Vifty-v<version>.zip' \ --pattern 'Vifty-v<version>.zip.sha256' \ --pattern 'Vifty-v<version>-artifact-summary.json' \ --pattern 'Vifty-v<version>-release-checklist.md' scripts/update-cask-checksum.sh \ --checksum-file "$RELEASE_ASSET_DIR/Vifty-v<version>.zip.sha256" \ --version <version>
The updater accepts the normal
shasum -a 256output from the release workflow, requires the target version, artifact name, and checksum to match the newly promoted published manifest, writes and validates a same-directory sibling candidate, then atomically renames it over the cask. It restores the original cask with the same rename discipline if final release-metadata validation fails. Then runscripts/render-release-facts.sh --write,scripts/check-release-manifest.sh, andscripts/render-release-facts.sh --checkbefore committing the manifest/cask/docs follow-up. The fact check also rejects prose that still labels an older version as current. -
Verify the public artifact and cask agree:
scripts/verify-release-artifact.sh --team-id "$APPLE_TEAM_ID"This downloads the cask URL unless
--artifact <zip>is provided, checks the SHA-256, extractsVifty.app, verifies bundle version, required executables, and bundledContents/Resources/schemasJSON Schemas exactly matching the manifest-authorized source contracts for release-readiness, release-artifact-summary, agent-cooling evidence summary/review, agent-run smoke evidence summary, validation-report-index, validation-review-result, andviftyctl, verifies the bundled read-only and supervised agent evidence collector scripts, validates plist files, checks Developer ID TeamID alignment including the LaunchDaemonVIFTY_XPC_ALLOWED_TEAM_ID, verifies hardened runtime flags and exact app entitlements, validates stapling, and runs Gatekeeper assessment. The release workflow runs the same verifier before publishing with--expected-shabecause the final cask checksum follow-up commit does not exist yet, and publishes the verifier's JSON summary as release evidence. The summary declaresschemaID: https://vifty.local/schemas/release-artifact-summary.schema.json, and release evidence review rejects summaries with a missing or drifted schema identity. When--summaryis provided and a release-trust check fails, including manifest or other early setup failures, the verifier writes a schema-valid failed summary naming the failing check so reviewers do not have to reconstruct the failure from transient logs alone.If this fails after an artifact has already been published, cut a corrected patch release instead of treating the Homebrew cask as trusted.
-
After publication, first populate the checksum follow-up worktree with the exact public manifest facts and cask checksum from step 8. Before committing or merging that follow-up, run
git fetch origin main --tagsandscripts/check-release-readiness.sh --mode developer-id --version <version> --repo Reedtrullz/Vifty --require-source-ref v<version> --json. The readiness checker intentionally does not treat an unpromotedcandidateas an authoritative public release, so it cannot pass until the follow-up worktree records the exact source-CI run, Release run, artifact digest, and trust states underpublishedRelease. Keep the passed schema-backed JSON with release notes or validation evidence. After the follow-up movesmain, rerun against the immutable tag or release commit rather thanorigin/main; the final JSON should showreleaseMode: "developer-id"plusrelease-source-ref,source-ci,release-workflow,release-secrets, andgithub-releaseall passed. -
After publication, verify on hardware with hardware-validation.md. Prefer
scripts/collect-validation-evidence.sh --app /Applications/Vifty.app --release-summary ./Vifty-v<version>-artifact-summary.json --release-checklist ./Vifty-v<version>-release-checklist.mdso release reports include the same read-only evidence bundle, includingreview-summary.tsv,review-summary.json,bundle-executables.tsv,schema-resources.tsv,capabilities-schema-resources.tsv,capabilities-contract.tsv,viftyctl-audit.json,release-artifact-summary.json,release-artifact-summary.tsv,release-checklist.md,release-checklist.tsv, bundle plist, LaunchDaemon TeamID, per-binary signing, notarization, Gatekeeper files, the release verifier result, and the release checklist. The collector marks the release-summary row nonzero if the verifier result does not pass or if its version does not match the installed app being tested; it marks the release-checklist row nonzero if the checklist title version does not match the installed app or if required follow-up sections are missing; it marks the capabilities-contract row nonzero if the installed CLI stops advertising the saferunLifecycle, direct prepare/restore lifecycle,policyStatusAvailable: true, wrapper resource discovery, metadata limits, or force-retry discovery fields. Before treating the installed release as trusted, runmake validation-evidence-review VALIDATION_EVIDENCE_BUNDLE=<evidence-dir> VALIDATION_EVIDENCE_REVIEW_MODE=release VALIDATION_EVIDENCE_REVIEW_SUMMARY=<evidence-dir>/review-result.jsonon the captured bundle and keep the schema-backedreview-result.jsonwith the report. -
For the first release containing the advisory checker, verify that only the exact Developer ID signed Vifty identity is eligible; automatic checks use only
https://api.github.com/repos/Reedtrullz/Vifty/releases/latest, run at most once per 24 hours, and can be disabled; availability acceptance requires a stable release with exactly the four canonical uploaded nonempty asset records; and Update to latest version opens the locally constructed tag page without downloading or installing executable code. These metadata checks are not archive, checksum-content, signed-tag, or notarization proof. Verify local ad-hoc, CI, source-first, and unsigned-dev builds make zero requests. The first capable public release must be installed manually becausev1.3.2cannot gain this code retroactively. For any later Sparkle installer, additionally verify thatSUFeedURLis HTTPS,SUPublicEDKeymatches the protected EdDSA private key, signed appcast settings are enabled,generate_appcastproduced the signed appcast for the canonicalVifty-v<version>.zip, and installation enters Vifty's existing Auto/System, daemon-quiescence, root-ledger, post-swap verification, and rollback transaction rather than replacing the bundle independently. -
Update compatibility.md only with report-backed results. Use
scripts/summarize-validation-reports.sh --input <reports-dir> --output-json <reports-dir>/compatibility-index.json --output-tsv <reports-dir>/compatibility-index.tsv --output-markdown <reports-dir>/compatibility-matrix.mdto index valid reviewedreview-result.jsonfiles and draft a conservative Markdown matrix; each review result declaresschemaID: https://vifty.local/schemas/validation-review-result.schema.json, the JSON index declaresschemaID: https://vifty.local/schemas/validation-report-index.schema.json, and the indexer rejects missing or drifted review-result schema IDs, malformed, non-read-only, cooling-mutating, unsupported-mode, unsupported install-source, invalid or missing required source SHA/checksum, mutable or missing source refs for source-build tag evidence, unsupportedagentRunSmokeResultvalues, missing or unsupported agent decision/recovery fields, malformedmanualControlActive, or contradictory passed review outputs. Leave model families as "needs validation" until supported-hardware rows are indexed asvalidated-hardware-evidencefrommanualSmokeTestResult: "passed-auto-restored". PreserverecommendedAgentAction,recommendedRecoveryAction,safeToRequestCooling,daemonControlPathReady,manualControlActive,agentRunSmokeResult, andagentRunSmokeSourceas separate developer-workload and readiness proof for guardedviftyctl run; do not use it as a substitute for manual fan-control smoke evidence. Use the generatedcompatibility-matrix.md, model-family counts, recommended agent action, recovery action,safeToRequestCooling, daemon control-path readiness, and manual-control ownership counts to spot unsafe readiness clusters before publishing compatibility claims.
You can verify the signing path locally without publishing:
make app CONFIGURATION=release \
SIGNING_IDENTITY="Developer ID Application: Example, Inc. (TEAMID)" \
VIFTY_XPC_ALLOWED_TEAM_ID="TEAMID"
plutil -p .build/Vifty.app/Contents/Library/LaunchDaemons/tech.reidar.vifty.daemon.plist
codesign --verify --deep --strict .build/Vifty.app
codesign -dvvv .build/Vifty.app 2>&1 | grep TeamIdentifierAd-hoc builds remain supported for local development and source-first tester convenience, but they are not trusted public binaries.