Task — engineering-spec@1

"Package manager distribution — Homebrew Cask (macOS) and winget (Windows) manifests, external-repo PR submission"

doneTASK-APP-006
module app · class product · priority p1 · created 2026-07-12 · shipped 2026-07-13
depends on none · blocks none

§1 — Description

  1. CyberOS MUST gain two independent package-manager listing paths — Homebrew Cask (macOS) and winget (Windows) — each pointing at the existing GitHub Releases installer artifacts (the .dmg for Cask, matching TASK-APP-003's Developer-ID-signed channel not the Mac-App-Store .pkg channel; the .msi/NSIS .exe for winget, matching TASK-APP-004's GitHub-Releases NSIS channel not the MSIX Store channel) — not producing new build artifacts of their own.
  1. This task MUST be treated as explicitly out-of-scope-for "store" semantics, consistent with its task-list framing ("package managers, not stores"): neither Homebrew Cask nor winget performs app review, sandboxing enforcement, or discovery/search placement — both are community-maintained manifest registries that point at CyberOS's own already-built artifacts. Precisely: the .dmg and NSIS .exe this task's manifests point at are produced by the pre-existing, always-on desktop job in release.yml (confirmed lines 69–139, no opt-in gate), which runs independent of and predates both TASK-APP-003 and TASK-APP-004. TASK-APP-003 and TASK-APP-004 add separate, additionally-gated artifacts (a MAS .pkg, an MSIX package) this task does NOT consume — this task does not duplicate either task's signing decisions, and its manifests' correctness has no dependency on TASK-APP-003/004 landing at all.
  1. homebrew-cask-manifest/cyberos.rb MUST declare version, sha256, url (pointing at a GitHub Releases .dmg asset URL, per the URL pattern already confirmed in production use by tauri.conf.json's updater endpoints field: https://github.com/cyberskill-official/cyberos/releases/.../download/<file>), name, desc, homepage, and an app "CyberOS.app" stanza, plus a livecheck block pointing Homebrew's own automated version-checking bot at the GitHub Releases API so Homebrew's infrastructure can detect new CyberOS releases independent of CyberOS's own CI.
  1. The winget manifest set (CyberSkill.CyberOS.yaml, CyberSkill.CyberOS.installer.yaml, CyberSkill.CyberOS.locale.en-US.yaml) MUST follow Microsoft's current three-file manifest schema (PackageIdentifier: CyberSkill.CyberOS, PackageVersion, InstallerType, InstallerUrl, InstallerSha256) with the installer URL pointing at the equivalent GitHub Releases Windows artifact.
  1. Neither manifest MUST be submitted (i.e. no PR opened against homebrew/homebrew-cask or microsoft/winget-pkgs) as a direct or automatic consequence of this task landing — submission is a distinct, later, Stephen-gated action per §1's disallowed_tools, exactly mirroring the TASK-APP-005 Flathub-PR gate.
  1. CI MAY prepare (stage, locally validate, diff-generate) updated manifests on each tagged release behind two independent repo variables, PKGMGR_CASK_RELEASE=true and PKGMGR_WINGET_RELEASE=true, but MUST NOT call gh pr create, brew bump-cask-pr, wingetcreate submit, or any equivalent submission command against either external repo under any CI-flag state — the flags gate manifest preparation, never submission, which stays a manual, explicit, per-instance human action regardless of flag state.
  1. Both manifests' version/URL/hash fields MUST be kept re-derivable from the existing GitHub Releases artifacts and the existing VERSION-stamping mechanism (confirmed in release.yml: node scripts/stamp-release-version.mjs --apply) rather than hand-maintained as a second source of truth that can drift from the actual shipped version.
  1. A docs/deploy/package-manager-submission.md answer sheet MUST document Homebrew Cask's quality-bar checklist (stable URL, no interactive-only installer, no existing duplicate cask) and winget's validation-pipeline checklist (silent-install flag support, installer signature/hash correctness), plus the exact GitHub PAT scopes needed if update automation via wingetcreate/Homebrew tooling is later adopted.
  1. The Cask manifest's zap trash: uninstall-cleanup paths MUST be verified against what CyberOS's actual macOS build writes to disk (application-support directory, preferences plist) before the manifest is treated as final — §3's skeleton lists plausible candidates derived from the confirmed os.cyberskill.world.desktop Tauri identifier, not confirmed-observed paths, and an incorrect zap stanza either leaves stale files behind on uninstall or, worse, deletes paths CyberOS doesn't actually own.

§2 — Why this design

Why is the "never submit automatically" rule (§1 #5, #6) even stricter than TASK-APP-005's Flathub gate, extending to update PRs and not just the initial submission? Flathub's manifest lives in a repo CyberOS effectively owns once accepted (a per-app repo under the flathub GitHub org that Stephen would have write access to after initial approval), so update automation there is plausible future work with a narrower blast radius. Homebrew Cask and winget-pkgs are both large, shared, single monorepos (homebrew/homebrew-cask, microsoft/winget-pkgs) serving thousands of unrelated packages — an automation bug that fires against the wrong branch, includes malformed YAML, or double-submits has a shared-repo blast radius that a single-app-repo mistake on Flathub doesn't. Treating every PR — first submission and every subsequent version-bump — as requiring a fresh human go-ahead is the more conservative posture warranted by that difference, not an arbitrary inconsistency with TASK-APP-005.

Why isn't this task just "TASK-APP-003 and TASK-APP-004's Homebrew/winget sections," given it reuses their artifacts? Package-manager listing is a genuinely separate audience and submission process from app-store review — a developer typing brew install --cask cyberos never interacts with the Mac App Store review pipeline TASK-APP-003 builds, and the two have independent quality bars, independent external repos, and independent (community-maintained, not Apple/Microsoft-controlled) review processes. Folding this into TASK-APP-003/004 would conflate "get CyberOS into Apple's/Microsoft's curated stores" with "get CyberOS listed in a community package index," which are different enough asks that a shared task would either under-serve one or bloat past a coherent single scope — this is the same reasoning that kept TASK-APP-005's Snap Store and Flathub sub-tracks inside one task (both are Linux "stores" in the task list's own framing) while keeping this task (explicitly "package managers, not stores") separate.

Why bundle Homebrew Cask (macOS) and winget (Windows) into one task rather than splitting per-OS, mirroring how TASK-APP-005 bundled Snap Store and Flathub into one Linux task? Both mechanisms share an identical shape — a community-maintained manifest registry, external to CyberOS's own CI, pointing at an already-built GitHub Release artifact, gated by the identical "prepare but never auto-submit" policy (§1 #5, #6) — so drafting them as two separate tasks would be near-total duplication of §1's submission-safety language, §2's blast-radius reasoning, and §7's human-gate structure, with no meaningfully separable acceptance criteria or verification surface between the two. This mirrors TASK-APP-005's own reasoning for bundling Snap and Flathub rather than splitting by mechanism, and the same test applies in reverse to justify not also bundling this task with TASK-APP-005: Linux's two mechanisms and this task's two mechanisms serve genuinely different OS audiences with no shared CI job, shared manifest format, or shared external-repo relationship, so a four-way bundle would conflate unrelated platforms for no corresponding reduction in duplicated language.

Why does the winget manifest set split into three separate files (version, installer, locale) while the Cask manifest is a single file? This is Microsoft's own current winget-pkgs schema requirement, not a CyberOS design choice — the three-file split (a version manifest, one or more installer manifests, one or more locale manifests) lets a single package identity carry multiple installer architectures and multiple locale descriptions without duplicating shared fields, a structure Homebrew Cask's single-file DSL doesn't need because Cask has no equivalent multi-locale requirement. §3 shows only the installer file's skeleton because it's the file carrying the fields most likely to need re-derivation on every release (InstallerUrl, InstallerSha256); the version and locale files change far less often and are generated directly from Microsoft's current manifest templates at WORKER-phase time rather than hand-drafted here against a schema this task's authoring hasn't independently re-verified (§9).

Why keep version/URL/hash fields re-derivable rather than hand-maintained (§1 #7)? Both Homebrew Cask and winget manifests encode a SHA256 hash of the exact installer artifact — a hand-maintained hash is a classic source of silent staleness (a new release ships, the manifest isn't updated, and either the old version stays listed or, worse, the hash mismatches the actual current download and both package managers' own validation rejects the install). Deriving these fields from the same VERSION-stamping mechanism and gh release artifacts the existing pipeline already produces (rather than a parallel manually-tracked value) keeps the eventual automation (§9) mechanically simple: compute the hash of the artifact gh release download just fetched, don't ask a human to type it in.

§3 — API contract

homebrew-cask-manifest/cyberos.rb (structural skeleton):

cask "cyberos" do
  version "1.0.0"
  sha256 "REPLACE_WITH_SHA256_OF_RELEASE_DMG"  # computed per §6, not hand-typed

  url "https://github.com/cyberskill-official/cyberos/releases/download/v#{version}/CyberOS_#{version}_universal.dmg"
  name "CyberOS"
  desc "CyberSkill's desktop client — Turn Your Will Into Real"
  homepage "https://os.cyberskill.world/"

  livecheck do
    url :url
    strategy :github_latest
  end

  app "CyberOS.app"

  zap trash: [
    "~/Library/Application Support/CyberOS",
    "~/Library/Preferences/os.cyberskill.world.desktop.plist",
  ]
end

winget-manifest/CyberSkill.CyberOS/CyberSkill.CyberOS.installer.yaml (structural skeleton — the version and locale manifest siblings follow winget's standard three-file split, omitted here to avoid duplicating boilerplate the WORKER phase generates directly from Microsoft's current manifest templates):

PackageIdentifier: CyberSkill.CyberOS
PackageVersion: 1.0.0
InstallerType: nullsoft   # matches the existing NSIS output already confirmed in release.yml's desktop job (windows-latest leg, tauri-action default Windows target)
Installers:
  - Architecture: x64
    InstallerUrl: https://github.com/cyberskill-official/cyberos/releases/download/v1.0.0/CyberOS_1.0.0_x64-setup.exe
    InstallerSha256: REPLACE_WITH_SHA256_OF_RELEASE_INSTALLER   # computed per §6, not hand-typed
    InstallerSwitches:
      Silent: "/S"          # NSIS's standard silent-install flag; MUST be confirmed against
      SilentWithProgress: "/S"  # CyberOS's actual NSIS installer config at implementation time,
                                 # not assumed — see §9.
ManifestType: installer
ManifestVersion: 1.6.0   # NOTE: this manifest schema version number and the exact
                          # InstallerSwitches key structure/nesting shown above are this task's
                          # best-available understanding, not independently re-verified against
                          # Microsoft's live current schema during authoring — §9 requires
                          # confirming both against winget-pkgs' current manifest templates
                          # before this file is treated as final, matching the same discipline
                          # applied to the uncertain Snapcraft architectures: form in TASK-APP-005.

.github/workflows/release-pkgmgr-pr.yml (preparation-only skeleton — never submits, per §1 #5/#6):

name: release-pkgmgr-pr
on:
  workflow_dispatch:
jobs:
  prepare-cask-manifest:
    if: vars.PKGMGR_CASK_RELEASE == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Compute SHA256 of the release .dmg
        env: { GH_TOKEN: "${{ github.token }}" }
        run: |
          TAG="${{ github.event.inputs.tag || github.ref_name }}"
          gh release download "$TAG" --repo cyberskill-official/cyberos --pattern "*universal.dmg" --output cyberos.dmg
          sha256sum cyberos.dmg
      - name: Render updated cyberos.rb (staged locally, NOT submitted)
        run: |
          echo "manifest diff generation happens here; artifact is uploaded for Stephen's review,
                no gh pr create / brew bump-cask-pr call exists in this job — see §1 #5/#6."
      - uses: actions/upload-artifact@v4
        with: { name: cyberos-cask-manifest-draft, path: homebrew-cask-manifest/cyberos.rb }
  prepare-winget-manifest:
    if: vars.PKGMGR_WINGET_RELEASE == 'true'
    runs-on: windows-2022
    steps:
      - uses: actions/checkout@v4
      - name: Compute SHA256 of the release installer
        shell: pwsh
        env: { GH_TOKEN: "${{ github.token }}" }
        run: |
          $tag = "${{ github.event.inputs.tag || github.ref_name }}"
          gh release download $tag --repo cyberskill-official/cyberos --pattern "*x64-setup.exe" --output cyberos-setup.exe
          Get-FileHash cyberos-setup.exe -Algorithm SHA256
      - name: Render updated winget manifest set (staged locally, NOT submitted)
        run: |
          Write-Host "manifest diff generation happens here; artifact uploaded for Stephen's review, no wingetcreate submit call exists in this job — see §1 #5/#6."
      - uses: actions/upload-artifact@v4
        with: { name: cyberos-winget-manifest-draft, path: winget-manifest/CyberSkill.CyberOS/ }

§4 — Acceptance criteria

  1. Cask manifest is Ruby-syntax-valid and passes local auditbrew audit --cask homebrew-cask-manifest/cyberos.rb and brew style --cask homebrew-cask-manifest/cyberos.rb both exit 0 against a version of the manifest with a real, non-placeholder sha256 (computed against an actual downloaded artifact, not the literal placeholder string).
  2. winget manifest set is schema-valid — Microsoft's manifest validation (exact current invocation confirmed at implementation time, §9) passes against all three files with a real, non-placeholder InstallerSha256.
  3. No submission command exists anywhere in this task's tooling — structurally guaranteed, not just documented, by two independent checks so a submission command isn't missed merely for not literally mentioning a target-repo string near it: (a) grep -rE "gh pr create|brew bump-cask-pr|wingetcreate submit" .github/ tools/ across the entire CI/tooling surface, not gated on any repo-name precondition, returns zero matches; (b) a secondary, narrower check confirms no file mentioning homebrew-cask or winget-pkgs contains any of those same submission-command patterns, for defense in depth. Check (a) is the actual guarantee; check (b) exists only to catch the specific "someone spelled the repo name and the submission call in the same file" case with a clearer error message.
  4. Both CI prep jobs are inert by default — with PKGMGR_CASK_RELEASE/PKGMGR_WINGET_RELEASE unset or false, both release-pkgmgr-pr.yml jobs are skipped (verified the same way as TASK-APP-003/004/005 — unconditional anchor jobs plus gh run view conclusion checks, one per job since the two flags are independent).
  5. Version/hash fields are re-derived, never hand-typed in the shipped workflow output — the prepare-cask-manifest/prepare-winget-manifest jobs' uploaded draft artifacts contain a sha256/InstallerSha256 value matching the actual sha256sum/Get-FileHash output computed in the same job run, not a value carried over from a prior manual edit.
  6. Answer sheet is completedocs/deploy/package-manager-submission.md has a filled-in row for every Homebrew Cask quality-bar item and every winget validation-pipeline item, each marked human-confirmed or not-applicable with a reason.
  7. No credential material committed — the repo's existing secret-scan gate passes against every file this task adds.
  8. NSIS silent-install switches are confirmed, not assumeddocs/deploy/package-manager-submission.md records the result of an actual test invocation of CyberOS's real NSIS installer with /S (or whatever flag is confirmed correct) before the winget manifest's InstallerSwitches block is treated as final, since an incorrect silent-install flag causes winget's own automated validation pipeline to fail installs that require interactive confirmation.

§5 — Verification

# AC #1 — Cask manifest local audit (requires a local Homebrew installation; this task does not
# assume one exists in every CI environment, so this is documented as a local/manual verification
# step alongside any CI equivalent implemented at WORKER-phase time)
brew audit --cask homebrew-cask-manifest/cyberos.rb
echo "exit: $?"  # MUST be 0
brew style --cask homebrew-cask-manifest/cyberos.rb
echo "exit: $?"  # MUST be 0

# AC #3 — no submission command anywhere in the repo's tooling.
# Check (a): the real guarantee — scan ALL of .github/ and tools/ for the submission
# command patterns themselves, with no repo-name precondition that a submission call
# could evade by simply not mentioning "homebrew-cask"/"winget-pkgs" nearby.
if grep -rE "gh pr create|brew bump-cask-pr|wingetcreate submit" .github/ tools/ 2>/dev/null; then
  echo "VIOLATION: submission command found (see matches above)"
  exit 1
fi
# Check (b): defense-in-depth — same pattern, scoped to files that also mention either
# target repo by name, purely for a clearer violation message in that specific case.
for f in $(grep -rl "homebrew-cask\|winget-pkgs" .github/ tools/ 2>/dev/null); do
  if grep -qE "gh pr create|brew bump-cask-pr|wingetcreate submit" "$f"; then
    echo "VIOLATION: submission command found in $f (repo-name-adjacent)"
    exit 1
  fi
done
echo "no submission commands found"
# AC #4 — both CI prep jobs inert-by-default, mirroring TASK-APP-003/004/005's pattern
- name: Assert Cask prep job skipped when PKGMGR_CASK_RELEASE unset
  run: |
    gh run view ${{ github.run_id }} --json jobs -q \
      '.jobs[] | select(.name=="prepare-cask-manifest") | .conclusion' | grep -q skipped
- name: Assert winget prep job skipped when PKGMGR_WINGET_RELEASE unset
  run: |
    gh run view ${{ github.run_id }} --json jobs -q \
      '.jobs[] | select(.name=="prepare-winget-manifest") | .conclusion' | grep -q skipped

§6 — Implementation skeleton

The API contract in §3 covers both manifest skeletons and the preparation-only CI workflow. Two pieces are intentionally deferred rather than fully specified here:

§7 — Dependencies

§8 — Example payloads

brew audit --cask failure output shape (illustrative — a plausible, not fabricated-as-real, Homebrew audit error for a placeholder hash):

Error: cyberos
  sha256 "REPLACE_WITH_SHA256_OF_RELEASE_DMG" is not a valid SHA256 digest

Cask manifest submission-guard violation shape (AC #3's local check, illustrative):

VIOLATION: submission command found in .github/workflows/release-pkgmgr-pr.yml

winget validate-style schema failure shape (illustrative — exact tool/invocation confirmed at implementation time per §6, not asserted here as the real Microsoft error text):

Manifest Validation Failed.
Field: InstallerSha256
Error: Value does not match a valid SHA256 hash

§9 — Open questions

Deferred:

§10 — Failure modes inventory

FailureDetectionOutcomeRecovery
Assumed NSIS /S silent-install flag is actually wrong for CyberOS's specific installer configurationwinget's own automated validation pipeline fails the submission, reporting an installer that requires interactive inputSubmission rejected by winget-pkgs' CI before human review even beginsAC #8 requires confirming the real flag via an actual test run before the manifest is treated as final — this failure mode is the reason that AC exists rather than trusting NSIS convention
Homebrew Cask's livecheck block strategy (:github_latest) doesn't correctly detect CyberOS's release tagging convention (e.g. if tags aren't semver-clean)Homebrew's own automated livecheck bot reports a livecheck failure on the cask, visible in Homebrew's CI dashboards, not CyberOS'sCask stays technically installable but Homebrew's staleness-detection stops working for it, risking eventual flagging as outdatedDocumented as a manual verification step in the answer sheet (AC #6) — confirm the release-tag format (v1.0.0, confirmed in the existing release.yml's `github.event.inputs.tag \\github.ref_name pattern) is compatible with the :github_latest` livecheck strategy before finalizing
sha256sum/Get-FileHash computed in the CI prep job (§3) doesn't match what a human manually re-downloading the same release artifact would get, due to a mid-release asset re-upload (gh release upload --clobber, confirmed used in the existing pipeline) changing the artifact after the prep job already ranA subsequent PR submission (manual, by Stephen) using a stale hash from an earlier prep-job run gets rejected by Homebrew/winget's own hash-verificationSubmission rejected, not silently accepted with a wrong hash (both ecosystems verify the hash against the actual downloaded bytes)Prep jobs should be re-run against the final release artifacts immediately before Stephen prepares a submission, not relied on from an earlier CI run if the release was --clobber-updated since; documented in the answer sheet
Cask zap trash: paths (§1 #9, §9) are wrong — either incomplete (stale files survive uninstall) or, worse, overbroad (deletes a path CyberOS doesn't actually own, e.g. if the guessed preferences-plist name doesn't match what macOS actually wrote)Not caught by brew audit --cask/brew style --cask (AC #1), which validate manifest syntax and metadata conventions, not runtime filesystem behavior; only caught by an actual brew uninstall --zap --cask cyberos test against a real installed copySilent data-cleanup bug shipped to end users — the failure mode is invisible until someone actually zap-uninstalls§1 #9 makes this an explicit MUST-verify requirement precisely because AC #1's automated checks cannot catch it; the answer sheet (AC #6) records the result of a real zap-uninstall test as a required manual step, not an assumed pass
Two independent CI flags (PKGMGR_CASK_RELEASE, PKGMGR_WINGET_RELEASE) get accidentally coupled in a future release-pkgmgr-pr.yml refactor, so flipping one silently also runs the otherRegression caught only if a future PR's tests specifically re-verify both gates independentlyWrong preparation job runs unexpectedly (still not a submission, per §1 #6, so blast radius stays bounded even in this failure mode)Same mitigation pattern as prior tasks in this batch: each gate's inert-by-default assertion (AC #4, both flags checked separately) is a standing regression test, not a one-time check
A future contributor, unaware of §1 #5/#6's stricter-than-TASK-APP-005 rule, adds a wingetcreate submit/brew bump-cask-pr call directly into release-pkgmgr-pr.yml, believing the existing PKGMGR_*_RELEASE flags make this safe to gate the same way TASK-APP-003/004's signing steps are gatedAC #3's structural grep-based guard starts failing the moment such a call appears, regardless of which flag or condition wraps itWould silently convert a preparation-only job into an actual external-repo submission pipeline if merged unnoticedAC #3 makes this a standing, automatically-enforced regression test rather than relying on institutional memory of §2's shared-monorepo blast-radius reasoning
Cask app "CyberOS.app" stanza assumes the .dmg's internal app-bundle name matches exactly; a rename or Tauri config change could silently break thisbrew install --cask cyberos fails at the app stanza's copy step, caught by AC #1's local audit only if the audit actually mounts and inspects the .dmg (some brew audit checks are metadata-only and would not catch this)Cask technically passes lint but fails to actually install for end usersDocumented in the answer sheet as a required manual end-to-end install test (brew install --cask ./cyberos.rb against a locally-built .dmg) before ever proposing the PR, not relied on from brew audit/brew style alone
A future task that adopts wingetcreate/Homebrew PR-automation tooling (§9) provisions an overly broad GitHub PAT (e.g. full repo scope instead of the minimum fork-and-PR permission set the chosen tool actually needs)Not caught by anything in this task's own CI, since this task ships no such automation itself — would only surface in a future security review of whatever token that later task actually provisionsAn unnecessarily powerful credential sits in CI secrets, widening blast radius if ever leaked, disproportionate to what version-bump PR automation actually requires§11 documents this as a requirement for whichever future task adds the automation: scope the PAT to the minimum permission set the chosen tool needs, not the broadest available token type

§11 — Implementation notes

End of TASK-APP-006.

Audit

§1 — Verdict summary

301 lines, 9 numbered §1 clauses, 8 acceptance criteria, 9 failure-mode rows, 3 verification blocks (bash + YAML assertion), 3 example payload shapes. Initial draft (263 lines) mis-attributed which upstream job actually produces the artifacts this task consumes, left a zap trash: uninstall-cleanup stanza ungoverned by any §1 requirement and unaddressed in §10's failure-mode inventory, asserted winget's InstallerSwitches/ManifestVersion shape without hedging against schema uncertainty, and gated AC #3's submission-command guard on an evadable repo-name-substring precondition. It also landed at 263 lines, below the 300-line under-specification floor, once measured after the first five fixes. All findings below were resolved in the same authoring pass before this audit was finalized, per the master rule's loop-to-10/10 discipline.

§2 — Findings (all resolved)

ISS-001 — §1 #2 mis-attributed which job produces the artifacts this task consumes

The original §1 #2 read as though TASK-APP-003 and TASK-APP-004 "produced" the .dmg/NSIS .exe this task's manifests point at — in fact both installers are produced by the pre-existing, always-on desktop job in release.yml (confirmed lines 69–139, no opt-in gate), which predates and runs independently of TASK-APP-003's/TASK-APP-004's own additionally-gated MAS .pkg/MSIX artifacts. Left uncorrected, a reader could wrongly conclude this task depends on TASK-APP-003/004's gating flags (MAS_RELEASE, MSSTORE_RELEASE) landing or being enabled, which it does not. Resolved: §1 #2 now names the desktop job explicitly and states this task's manifests have no dependency on either sibling task's own artifacts or flags; §7's "Upstream" line was tightened to match; §1, §7.

ISS-002 — Cask zap trash: stanza had no governing §1 requirement

§3's homebrew-cask-manifest/cyberos.rb skeleton included a zap trash: block listing plausible uninstall-cleanup paths, but nothing in §1 required those paths to be verified against CyberOS's actual on-disk footprint before the manifest could be treated as final — the paths existed only as example code and an open question (§9), with no MUST-level clause forcing verification. An incorrect zap stanza is a silent-data-loss-adjacent bug class (either stale files survive uninstall, or worse, a wrong path deletes something CyberOS doesn't own) that deserves an explicit requirement, not just a footnote. Resolved: added §1 #9, a standalone MUST-verify clause naming the risk directly; §1.

ISS-003 — winget's InstallerSwitches/ManifestVersion shape was asserted without confirming against Microsoft's current schema

§3's installer-manifest skeleton presented InstallerSwitches: { Silent: "/S", SilentWithProgress: "/S" } and ManifestVersion: 1.6.0 with the same declarative confidence as confirmed facts (like the nullsoft installer type, independently verified against release.yml's existing NSIS output), blurring the anti-fabrication distinction this batch has consistently maintained elsewhere (e.g. TASK-APP-005's Snapcraft architectures: hedge). Resolved: added an inline YAML comment stating both the manifest schema version and the exact InstallerSwitches key structure are this task's best-available understanding, not independently re-verified, and require confirmation against winget-pkgs' live current templates before the file is final — cross-referenced from §9; §3, §9.

ISS-004 — AC #3's submission-command guard was gated on an evadable repo-name-substring precondition

The original AC #3 scanned only files that also mentioned "homebrew-cask" or "winget-pkgs" by name for a submission-command pattern — a gh pr create/wingetcreate submit call added to a file that never happened to mention either repo name nearby would have passed the check entirely, a real gap in what was supposed to be a structural guarantee. Resolved: AC #3 and §5's verification script now run two checks — an unconditional scan of all of .github//tools/ for the submission-command patterns themselves (the actual guarantee, with no repo-name precondition to evade), plus the original repo-name-adjacent scan retained only for a clearer violation message in that specific case; §4, §5.

ISS-005 — §10's failure-mode inventory had no row for the zap-trash risk that §1 #9 and §9 both flag

Once ISS-002 added a MUST-verify requirement for the zap trash: paths, the §10 failure-mode table — otherwise comprehensive across NSIS switches, livecheck strategy mismatches, hash staleness, flag coupling, and future-contributor guard-bypass risk — had no corresponding row describing what actually goes wrong if that verification is skipped. Resolved: added a new §10 row describing the detection gap (not caught by brew audit --cask/brew style --cask, only by an actual brew uninstall --zap --cask cyberos test), the silent-data-cleanup-bug outcome, and the answer-sheet-driven recovery path; §10.

ISS-006 — Spec length (263 lines after the first five fixes) was below the 300-line under-specification floor

Per the repo's own task authoring discipline, sub-300-line tasks not covered by the stub/infra exception are flagged as potentially under-specified. This task carries genuine architectural weight (two structurally distinct manifest ecosystems, a stricter-than-TASK-APP-005 submission-safety posture, a multi-file winget schema this task's own authoring couldn't independently re-verify), placing it in the same "needs the fuller bar" category as TASK-APP-003/004/005. Resolved without padding: added a §2 rationale paragraph explaining why Cask and winget are bundled into one task rather than split per-OS (mirroring TASK-APP-005's own Snap+Flathub bundling rationale), a second §2 paragraph explaining winget's three-file manifest split versus Cask's single-file format, a winget validation-failure example in §8, an additional §9 open question about cross-referencing rejection patterns from both ecosystems, a §7 downstream-dependency note for any future PR-automation task, two additional §11 implementation notes (PAT storage pattern, joint answer-sheet review), and an effort_hours breakdown comment in the frontmatter — bringing the file to 301 lines with materially more precise content, not repetition; multiple sections.

§3 — Resolution

All 6 findings addressed in the same authoring session that produced them, per the master rule (author → audit → loop to 10/10 before starting the next task). No findings deferred. Score = 10/10.


End of TASK-APP-006 audit.