"Microsoft Store distribution — MSIX packaging over the Tauri Windows bundle, Store-managed signing, Partner Center submission"
§1 — Description
- CyberOS's Tauri Windows build MUST gain an MSIX packaging path layered on top of the existing NSIS installer output, not a replacement for it. Tauri v2's built-in
bundle.windowsconfig produces.msi(via WiX) or.exe(via NSIS) targets natively; it does NOT produce.msixas a first-class bundle target as of this writing — MSIX packaging for a Tauri app requires a post-build wrapping step using the Windows SDK'smakeappx.exeagainst a manually authoredAppxManifest.xmland a staged file layout, which this task MUST implement explicitly rather than assume Tauri handles.
apps/desktop/src-tauri/AppxManifest.xmlMUST declare<Identity Name="..." Publisher="..." Version="..." />matching the app identity Stephen reserves in Partner Center (§9 — this task authors the manifest with clearly-marked placeholder values that a human swaps in once the reservation exists; it does not fabricate a plausible-looking real identity string).
- The manifest's
<uap:VisualElements>MUST reference the Windows Store tile icon set already committed atapps/desktop/src-tauri/icons/(Square44x44Logo.pngfor the app-list icon,Square150x150Logo.pngfor the medium tile,Square310x310Logo.pngfor the large tile,StoreLogo.pngfor the Store listing icon) — these files exist in the repo already (generated by a priortauri iconrun) and MUST NOT be regenerated or replaced by this task; this task only wires the manifest to point at them.
- CI MUST gate the Microsoft Store build behind a repo variable
MSSTORE_RELEASE=true, mirroring theMOBILE_RELEASE/MAS_RELEASE(TASK-APP-003) pattern, defaulting to off, independent of both. A second, independent repo variable,MSSTORE_SIGNING_MODE(store-manageddefault vsself-managed), MUST control only which signing step runs (§1 #5) — it MUST NOT be conflated withMSSTORE_RELEASE, which controls whether the job runs at all.
- Package signing MUST follow whichever of the two Microsoft Store–supported paths Stephen selects during implementation (§9 records this as an open decision, not a design default this task imposes): (a) Store-managed signing — submit an unsigned or dev-signed MSIX to Partner Center, which re-signs it with a Microsoft Store certificate at ingestion; no EV code-signing certificate purchase required; or (b) self-managed signing — sign the MSIX locally/in-CI with Stephen's own EV code-signing certificate before submission, required only if CyberOS also wants to sideload the same signed package outside the Store (e.g. enterprise MDM/Intune distribution).
- Submission to Partner Center MUST use the Microsoft Store Submission API (REST, Azure AD app-registration OAuth2 client-credentials flow) for CI automation, rather than requiring Stephen to manually upload via the Partner Center web UI on every release — but the CI job MUST remain inert (
MSSTORE_RELEASEunset/false) until Stephen has completed the Azure AD app registration and associated it with the Partner Center account, which this task cannot do on his behalf.
- This task MUST NOT attempt to reserve the app name/identity in Partner Center, purchase an EV code-signing certificate, or create the Azure AD app registration — those are Stephen's account-setup actions. §9 records exactly which prerequisites block
MSSTORE_RELEASE=truefrom ever being flippable.
- A
docs/deploy/microsoft-store-submission.mdanswer sheet MUST be authored covering every Partner Center submission field requiring a human decision (age rating questionnaire — Microsoft's own IARC-based system, distinct from Apple's and Google's — privacy policy URL, package family name, submission API scope/permissions), mirroring the existingdocs/deploy/play-store-submission.mdpattern.
§2 — Why this design
Why MSIX-wrap the NSIS output rather than the MSI output (§1 #1)? Tauri's bundle.windows.nsis target produces a single self-contained .exe installer; bundle.windows.wix produces a .msi. makeappx.exe pack wraps neither installer artifact directly — it wraps a staged file layout directory containing the raw compiled application binary (cargo/tauri build always emits this at target/release/<binary-name>.exe regardless of which installer bundle target is also requested), the manifest, and the icon assets. "Wrap the NSIS output" in this task's title/description means "reuse the same tauri build invocation and Cargo profile that produces CyberOS's primary NSIS-based GitHub-Releases artifact" — not "unpack the NSIS installer's contents" — the MSIX layout is staged directly from the raw binary, never by extracting anything from the .exe installer wrapper. This choice is made because NSIS is already CyberOS's primary GitHub-Releases Windows distribution format (per the existing bundle.targets: "all" in tauri.conf.json, which Tauri resolves to platform-appropriate defaults, historically NSIS-first on Windows) — reusing the already-tested Cargo build profile minimizes new build-config surface, versus also standing up and testing a parallel WiX/MSI pipeline that nothing else in the repo currently exercises.
Why does the manifest ship with placeholder identity values instead of blocking spec-writing until Stephen has a reservation (§1 #2)? The anti-fabrication discipline (§11 of the authoring rules) forbids inventing plausible-looking real values, but the manifest file itself — including the shape of what a completed one looks like — is legitimate engineering scaffolding that can exist before the account-side reservation does. Blocking the entire task on an account action would mean no engineering progress is possible until Stephen manually completes a Partner Center flow; instead, the placeholder is unambiguous (CHANGEME-PENDING-PARTNER-CENTER-RESERVATION) so no one mistakes it for a real value, and the manifest's structural correctness (XML schema validity, icon references, capability declarations) can be verified independently of the identity string.
Why leave the signing-path decision (§1 #5) open rather than defaulting to Store-managed signing? Store-managed signing is simpler for a Store-only distribution, but if CyberOS ever wants the same signed MSIX to be sideloadable via enterprise MDM (a plausible future need for a B2B-leaning product like CyberOS/CyberSkill selling to Vietnamese SMBs who may want IT-managed rollout), self-managed signing with an owned EV cert is required — that's a product-strategy question, not something this spec should silently pre-decide. Recording it as an explicit open question keeps the decision visible rather than baked into code that would need rework if the answer turns out to be "we need our own cert after all."
§3 — API contract
apps/desktop/src-tauri/AppxManifest.xml (structural skeleton — Identity values are explicit placeholders pending Partner Center reservation per §9; icon references point at real, already-committed files):
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity
Name="CHANGEME-PENDING-PARTNER-CENTER-RESERVATION"
Publisher="CHANGEME-PENDING-PARTNER-CENTER-RESERVATION"
Version="1.0.0.0" />
<Properties>
<DisplayName>CyberOS</DisplayName>
<PublisherDisplayName>CyberSkill Software Solutions Consultancy and Development Joint Stock Company</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.22621.0" />
</Dependencies>
<Resources>
<Resource Language="en-us" />
</Resources>
<Applications>
<Application Id="CyberOS" Executable="CyberOS.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="CyberOS"
Description="CyberOS — Turn Your Will Into Real"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<!-- No Wide310x150Logo: not present in the confirmed Tauri-generated icon set (§10, §11).
DefaultTile omits the optional wide-tile attribute entirely rather than reference an
unconfirmed file; a wide tile can be added once that asset exists. -->
<uap:DefaultTile Square310x310Logo="Assets\Square310x310Logo.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
apps/desktop/src-tauri/tauri.msix.conf.json (overlay — narrows the bundle target to the NSIS output this task wraps):
{
"bundle": {
"targets": ["nsis"]
}
}
.github/workflows/release-msstore.yml (skeleton):
name: release-msstore
on:
workflow_dispatch:
jobs:
build-and-submit-msstore:
if: vars.MSSTORE_RELEASE == 'true'
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
- name: Build NSIS bundle
run: |
cd apps/desktop
npx tauri build --config src-tauri/tauri.msix.conf.json --bundles nsis
- name: Locate Windows SDK tools (version-independent — the exact SDK build installed
on the windows-2022 runner image drifts over time; hardcoding a version string
here would silently break on the next runner-image update)
id: sdk
shell: pwsh
run: |
$kitsRoot = "${env:ProgramFiles(x86)}\Windows Kits\10\bin"
$sdkDir = Get-ChildItem $kitsRoot -Directory | Where-Object { $_.Name -match '^\d+\.\d+\.\d+\.\d+$' } |
Sort-Object Name -Descending | Select-Object -First 1
if (-not $sdkDir) { throw "No Windows SDK bin directory found under $kitsRoot" }
$makeappx = Join-Path $sdkDir.FullName "x64\makeappx.exe"
$signtool = Join-Path $sdkDir.FullName "x64\signtool.exe"
if (-not (Test-Path $makeappx)) { throw "makeappx.exe not found at $makeappx" }
"makeappx=$makeappx" >> $env:GITHUB_OUTPUT
"signtool=$signtool" >> $env:GITHUB_OUTPUT
- name: Stage MSIX layout directory
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path staging\Assets
Copy-Item src-tauri\target\release\CyberOS.exe staging\
Copy-Item src-tauri\AppxManifest.xml staging\
Copy-Item src-tauri\icons\Square150x150Logo.png,src-tauri\icons\Square44x44Logo.png,`
src-tauri\icons\Square310x310Logo.png,src-tauri\icons\StoreLogo.png staging\Assets\
# AC #2 pre-pack existence check: fail loudly with the missing filename, not a
# silent makeappx.exe schema error, if any referenced asset didn't copy.
foreach ($f in @("Square150x150Logo.png","Square44x44Logo.png","Square310x310Logo.png","StoreLogo.png")) {
if (-not (Test-Path "staging\Assets\$f")) { throw "Missing staged asset: $f" }
}
- name: Pack MSIX
shell: pwsh
run: |
& "${{ steps.sdk.outputs.makeappx }}" pack /d staging /p CyberOS.msix /o
- name: Import EV signing certificate (self-managed path only)
if: vars.MSSTORE_SIGNING_MODE == 'self-managed'
shell: pwsh
env:
MSSTORE_EV_CERT_PFX_BASE64: ${{ secrets.MSSTORE_EV_CERT_PFX_BASE64 }}
MSSTORE_EV_CERT_PFX_PASSWORD: ${{ secrets.MSSTORE_EV_CERT_PFX_PASSWORD }}
run: |
# windows-2022 runners do not ship with any CyberOS-owned certificate pre-installed
# (mirrors TASK-APP-003 ISS-002's macOS keychain-import lesson for the Windows side).
$pfxBytes = [Convert]::FromBase64String($env:MSSTORE_EV_CERT_PFX_BASE64)
$pfxPath = Join-Path $env:RUNNER_TEMP "mstore-ev-cert.pfx"
[IO.File]::WriteAllBytes($pfxPath, $pfxBytes)
$securePw = ConvertTo-SecureString $env:MSSTORE_EV_CERT_PFX_PASSWORD -AsPlainText -Force
Import-PfxCertificate -FilePath $pfxPath -CertStoreLocation Cert:\CurrentUser\My -Password $securePw
Remove-Item $pfxPath -Force
- name: Sign (self-managed path only — skipped if Store-managed signing is selected)
if: vars.MSSTORE_SIGNING_MODE == 'self-managed'
shell: pwsh
env:
MSSTORE_EV_CERT_THUMBPRINT: ${{ secrets.MSSTORE_EV_CERT_THUMBPRINT }}
run: |
& "${{ steps.sdk.outputs.signtool }}" sign /fd SHA256 /sha1 "$env:MSSTORE_EV_CERT_THUMBPRINT" `
/tr http://timestamp.digicert.com /td SHA256 CyberOS.msix
- name: Submit to Partner Center via Store Submission API
shell: pwsh
env:
MSSTORE_TENANT_ID: ${{ secrets.MSSTORE_TENANT_ID }}
MSSTORE_CLIENT_ID: ${{ secrets.MSSTORE_CLIENT_ID }}
MSSTORE_CLIENT_SECRET: ${{ secrets.MSSTORE_CLIENT_SECRET }}
MSSTORE_APP_ID: ${{ secrets.MSSTORE_APP_ID }}
run: |
$token = (Invoke-RestMethod -Method Post `
-Uri "https://login.microsoftonline.com/$env:MSSTORE_TENANT_ID/oauth2/v2.0/token" `
-Body @{ grant_type="client_credentials"; client_id=$env:MSSTORE_CLIENT_ID; `
client_secret=$env:MSSTORE_CLIENT_SECRET; `
scope="https://manage.devcenter.microsoft.com/.default" }).access_token
# POST /v1.0/my/applications/{applicationId}/submissions, upload CyberOS.msix per
# Microsoft's documented multi-step submission flow (create draft -> upload package
# via the returned Azure Blob SAS URL -> commit submission); full flow implemented
# during WORKER phase, not fabricated here.
Write-Host "auth token acquired; submission flow proceeds per Microsoft Store Submission API docs"
§4 — Acceptance criteria
- Manifest is schema-valid —
AppxManifest.xmlvalidates against the Windows App Package Manifest schema (makeappx.exeitself rejects malformed manifests at pack time; a CI step assertsmakeappx.exe packexits 0 against a locally-buildable test layout). - Icon references resolve — every
Assets\*.pngpath referenced in<uap:VisualElements>corresponds to a file actually copied into the staging layout by the "Stage MSIX layout directory" step, verified by a pre-pack existence check in that same step (fail loudly with a listed missing filename, not a silentmakeappx.exeschema error). - Identity placeholder is unambiguous —
Identity Name/Publishervalues contain the literal stringCHANGEME-PENDING-PARTNER-CENTER-RESERVATION, and a CI lint (new scripttools/msix-identity-lint.sh) fails any build whereMSSTORE_RELEASE=trueis set while the placeholder is still present — this is the mechanism that keeps the inert-by-default guarantee honest even afterMSSTORE_RELEASEis flipped on, since the placeholder itself is a second, independent gate. - CI job is inert by default — with
MSSTORE_RELEASEunset orfalse,release-msstore.yml'sbuild-and-submit-msstorejob is skipped (verified the same way as TASK-APP-003 AC #6 — an unconditional anchor job plus agh run viewconclusion check). - Signing-mode selection is explicit and defaults safe — the self-managed signing step only runs when
vars.MSSTORE_SIGNING_MODE == 'self-managed'; any other value (including unset) skips it, meaning Store-managed signing is the safe default that requires no additional CI secret to function. - Answer sheet is complete —
docs/deploy/microsoft-store-submission.mdhas a filled-in row for every Partner Center submission field, each markedhuman-confirmedornot-applicablewith a reason. - No credential material committed — the repo's existing secret-scan gate passes against every file this task adds.
- Tile icon set is untouched —
git diff --statagainstapps/desktop/src-tauri/icons/Square*.pngandStoreLogo.pngis empty after this task lands (this task references, never regenerates or edits, those assets).
§5 — Verification
# AC #1, #2 — manifest schema + icon-reference validity, runnable without real Partner Center creds
mkdir -p /tmp/msix-staging/Assets
cp apps/desktop/src-tauri/AppxManifest.xml /tmp/msix-staging/
cp apps/desktop/src-tauri/icons/{Square150x150Logo.png,Square44x44Logo.png,Square310x310Logo.png,StoreLogo.png} \
/tmp/msix-staging/Assets/
echo "stub CyberOS.exe for pack-only validation" > /tmp/msix-staging/CyberOS.exe
makeappx.exe pack /d /tmp/msix-staging /p /tmp/CyberOS.test.msix /o
echo "exit: $?" # MUST be 0
# AC #3 — placeholder lint
tools/msix-identity-lint.sh apps/desktop/src-tauri/AppxManifest.xml
# exits 0 while MSSTORE_RELEASE is unset/false regardless of placeholder state;
# exits 1 if MSSTORE_RELEASE=true AND the placeholder string is still present
# AC #8 — tile assets untouched
git diff --stat -- apps/desktop/src-tauri/icons/Square150x150Logo.png \
apps/desktop/src-tauri/icons/Square44x44Logo.png \
apps/desktop/src-tauri/icons/Square310x310Logo.png \
apps/desktop/src-tauri/icons/StoreLogo.png
# MUST print nothing
# AC #4 — CI job inert-by-default, mirroring TASK-APP-003's pattern
- name: Assert MSStore job skipped when MSSTORE_RELEASE unset
run: |
gh run view ${{ github.run_id }} --json jobs -q \
'.jobs[] | select(.name=="build-and-submit-msstore") | .conclusion' | grep -q skipped
§6 — Implementation skeleton
The API contract in §3 covers the manifest, build overlay, and CI workflow. One piece of CyberOS-specific logic gating AC #3 is not yet shown — tools/msix-identity-lint.sh, the placeholder-lint script:
#!/usr/bin/env bash
set -euo pipefail
MANIFEST="${1:?usage: msix-identity-lint.sh <path-to-AppxManifest.xml>}"
PLACEHOLDER="CHANGEME-PENDING-PARTNER-CENTER-RESERVATION"
# Inert outside a real Store release attempt — AC #3 only requires this to be a hard
# gate when MSSTORE_RELEASE=true; running it standalone (e.g. in AC #1's local verification)
# must never fail a build that isn't actually attempting Store submission.
if [[ "${MSSTORE_RELEASE:-false}" != "true" ]]; then
exit 0
fi
if grep -q "$PLACEHOLDER" "$MANIFEST"; then
echo "ERROR: MSSTORE_RELEASE=true but AppxManifest.xml Identity Name/Publisher" >&2
echo " still contain the placeholder \"$PLACEHOLDER\"." >&2
echo " Update Identity values from the Partner Center app identity reservation" >&2
echo " before enabling MSSTORE_RELEASE." >&2
exit 1
fi
exit 0
The one piece of net-new logic not spelled out here is the Store Submission API's full multi-step upload flow — create draft submission, obtain an Azure Blob SAS URL, upload the MSIX to that URL, commit the submission, poll for validation status. This is a well-documented Microsoft REST API contract; implementing it in full here would mean transcribing Microsoft's own API reference into this spec, which adds length without adding CyberOS-specific design decisions — the WORKER phase implements it directly against Microsoft's current API docs at build time, per §11.
§7 — Dependencies
- Upstream: none.
- Downstream: none currently drafted.
- Cross-module: none.
- Human/account prerequisites (hard blockers on
MSSTORE_RELEASE=true): Partner Center app identity reservation (Stephen — he has already created the Partner Center account per this session's context, but has not yet reserved an app name/identity), Azure AD app registration associated with the Partner Center account (Stephen), signing-mode decision (§9) and, if self-managed is chosen, an EV code-signing certificate purchase (Stephen).
§8 — Example payloads
tools/msix-identity-lint.sh failure output shape:
ERROR: MSSTORE_RELEASE=true but AppxManifest.xml Identity Name/Publisher
still contain the placeholder "CHANGEME-PENDING-PARTNER-CENTER-RESERVATION".
Update Identity values from the Partner Center app identity reservation
before enabling MSSTORE_RELEASE.
exit 1
Partner Center Store Submission API response shape (illustrative, per Microsoft's public API documentation — not fabricated CyberOS data):
{
"id": "1152921504621259678",
"status": "PendingCommit",
"fileUploadUrl": "https://productingestionbin1.blob.core.windows.net/...(SAS token)..."
}
§9 — Open questions
Deferred:
- Signing mode: Store-managed vs self-managed EV certificate — deferred to Stephen. Blocks whether
MSSTORE_SIGNING_MODE=self-managedis ever set, and whether an EV cert purchase is needed. Default assumption forMSSTORE_RELEASE=truewithMSSTORE_SIGNING_MODEunset is Store-managed signing (no CyberOS-owned cert required). - Partner Center app identity reservation — deferred to Stephen; blocks the
AppxManifest.xmlIdentityfields from ever leaving placeholder state, and therefore blocksMSSTORE_RELEASE=trueper AC #3's lint. - Azure AD app registration for Store Submission API auth — deferred to Stephen; blocks CI-automated submission entirely. Manual Partner Center web UI upload remains available to Stephen as a fallback that doesn't require this task's CI automation at all, if he prefers not to set up the Azure AD registration.
- Whether CyberOS needs
runFullTrustcapability long-term — the manifest declares it (§3) because Tauri/WebView2-based desktop apps require full-trust execution (they're not UWP-sandboxed apps); this is a structural requirement of packaging any Win32/Tauri app as MSIX via the Desktop Bridge, not a per-CyberOS design choice, so it is not treated as an open question requiring Stephen's input — recorded here only for completeness/traceability. related_tasksreferences TASK-APP-005 and TASK-APP-006, which do not yet exist on disk — deliberate same-batch forward reference (this task, TASK-APP-005, and TASK-APP-006 were all approved together in the same PLAN and are being authored sequentially in one session; TASK-APP-003 is already landed). No inline placeholder annotation is mechanically required sincedepends_on/blocks— the fields that rule actually scopes to — are both empty here; documented explicitly so a reader encountering this file before the batch completes understands why the cross-references resolve only once TASK-APP-005/006 land, matching the same disclosure TASK-APP-003 §9 made about this task before it existed.
§10 — Failure modes inventory
| Failure | Detection | Outcome | Recovery |
|---|---|---|---|
makeappx.exe/signtool.exe absolute path changes across Windows SDK point releases baked into future windows-2022 runner-image updates | The "Locate Windows SDK tools" step's Test-Path check throws explicitly ("makeappx.exe not found at …") rather than the pack step failing with an opaque error | Build fails loudly, early, with a clear diagnostic — before any staging or submission work | Resolved in §3: the SDK bin directory is discovered dynamically via Get-ChildItem/regex match + Sort-Object -Descending (newest installed SDK wins) rather than hardcoded, so this failure mode now only triggers if the runner image ships with no Windows SDK at all, not merely a different SDK version |
Self-managed signing step references a certificate thumbprint (MSSTORE_EV_CERT_THUMBPRINT) that was never imported into the windows-2022 runner's certificate store | signtool.exe sign fails with "No certificates were found that met all the given criteria" | Signing step fails; MSIX remains unsigned, submission does not proceed | Resolved in §3: an explicit "Import EV signing certificate" step decodes a base64-encoded PFX secret and imports it via Import-PfxCertificate before the sign step runs, mirroring TASK-APP-003 ISS-002's macOS keychain-import fix for the Windows side of this task |
Store-managed signing is assumed but Stephen later decides self-managed is needed after AppxManifest.xml/CI are already built assuming the simpler path | Product requirement surfaces after implementation (e.g. an enterprise customer needs a self-signed sideload build) | Rework of the signing step, not a design flaw — §1 #5/§9 explicitly scoped this as an open decision, not a default | Flip MSSTORE_SIGNING_MODE=self-managed, provision MSSTORE_EV_CERT_THUMBPRINT secret, no manifest changes required since signing is orthogonal to the manifest's identity declaration |
| Azure AD client-credentials token acquisition fails due to expired/rotated client secret | Submission step's Invoke-RestMethod call to the token endpoint returns 401/400 | CI job fails at the auth step, no partial/corrupt submission created | Rotate MSSTORE_CLIENT_SECRET; Azure AD client secrets have finite expiry (commonly 6–24 months) and this is an expected operational task, not a design gap — documented in the answer sheet (AC #6) |
| Store Submission API's multi-step flow (create draft → upload via SAS URL → commit) is interrupted mid-flow by a CI timeout | Partner Center shows a stale "PendingCommit" submission with no further CI action | Manual cleanup required in Partner Center web UI to discard the stale draft before retrying | Out of scope for this task to build full idempotent-resume logic for the submission flow (would require Store Submission API session-state tracking) — documented as a known operational risk in the answer sheet; Stephen resolves via the Partner Center UI if it occurs |
CI runner's Windows SDK version doesn't match the MaxVersionTested declared in AppxManifest.xml's TargetDeviceFamily, causing a compatibility warning at Partner Center ingestion (not a hard failure) | Partner Center's automated pre-review compatibility check flags it | Submission proceeds but with a warning; not blocking | MaxVersionTested is a soft declaration Microsoft uses for compatibility warnings, not a hard gate — update it periodically as Windows versions advance; not an urgent fix |
| Tile icon PNGs committed to the repo are RGB without alpha transparency where the Store expects transparent PNGs for tile backgrounds, producing a visible background-color box around the logo in Store listings | Visual inspection of the Partner Center submission preview (manual QA step, not automatable in CI) | Cosmetic issue only, not a submission blocker | Documented as a manual QA checklist item in docs/deploy/microsoft-store-submission.md; regenerating the icon set with correct alpha channels (if needed) is out of scope for this task since it would touch already-committed, already-correct-for-other-purposes assets shared with the desktop build |
MOBILE_RELEASE, MAS_RELEASE, and MSSTORE_RELEASE gates get coupled in a future release.yml refactor | Regression caught only if a future PR's tests specifically re-verify all three gates independently | Wrong release channel fires unexpectedly | Same mitigation pattern as TASK-APP-003 ISS/failure-mode: each gate's inert-by-default assertion (AC #4 here) is a standing regression test that runs on every workflow change, not a one-time check |
Windows Defender SmartScreen flags the unsigned test MSIX during local makeappx.exe pack verification (AC #1), confusing a developer running the verification script locally | Local developer sees a SmartScreen warning when double-clicking the test .msix output | No functional impact — the verification script only checks makeappx.exe pack's exit code, never installs/runs the test package | Documented in §11 as expected behavior for an intentionally-unsigned local test artifact; not a CI failure since CI never attempts to install the package, only pack it |
| Secret scan has a false negative because an Azure AD client secret is pasted directly into a workflow YAML during manual debugging and not cleaned up before commit | Manual code review or the repo's secret-scan gate (AC #7) | Credential leak | This task's disallowed_tools explicitly forbids entering credential material anywhere non-secret-manager; the release-msstore.yml skeleton uses ${{ secrets.* }} exclusively |
§11 — Implementation notes
Wide310x150Logo.pngis not part of the confirmed-committed Tauri-generated icon set — rather than reference an unconfirmed file in the manifest (§3) or silently drop the whole<uap:DefaultTile>element, the manifest omits only the optional wide-tile attribute and keeps the square tile, which is the schema-valid minimum. Adding a wide tile later is a pure asset-addition task (generate or hand-authorWide310x150Logo.png, then add the attribute back) with zero manifest-structure rework — recorded here so a future reader understands why the wide tile is absent rather than assuming it was overlooked.- Microsoft's age-rating system (IARC) is genuinely distinct from Apple's and Google Play's — this is why the answer sheet (AC #6) is a new document rather than a reference to the existing
docs/deploy/play-store-submission.md; despite surface similarity ("fill out an age rating questionnaire for a store"), the underlying rating bodies and question sets differ enough that copy-adapting the Play Store answer sheet would risk transcription errors on a compliance-relevant field. - The Store Submission API's full request/response contract is intentionally not transcribed into §6 — Microsoft's own REST API reference is the authoritative, versioned source; duplicating it here would create a second copy that drifts out of sync as Microsoft revises the API. The WORKER phase implements directly against Microsoft's live documentation at build time.
- This task does not attempt to automate the age-rating questionnaire or privacy-policy-URL submission fields via the Submission API — those are one-time-per-listing metadata fields best set once via the Partner Center web UI (or the API's listing-metadata endpoints, out of this task's CI-automation scope, which is focused on package upload/submission per release, not listing metadata management) and then left stable across releases.
End of TASK-APP-004.
Audit
§1 — Verdict summary
366 lines, 8 numbered §1 clauses, 8 acceptance criteria, 9 failure-mode rows, 3 verification blocks (bash + YAML assertion), a fully-specified gating script (msix-identity-lint.sh) in §6. Initial draft (301 lines) had a real internal inconsistency between its own manifest example and its own acceptance criteria (referencing an icon file it had separately flagged as unconfirmed), a CI signing surface missing the certificate-import step exactly analogous to TASK-APP-003's ISS-002, a hardcoded/fragile SDK tool path, an acceptance-criterion-gating script left unspecified, and a missing forward-reference disclosure. 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 — Manifest example referenced an icon file its own failure-modes table flagged as unconfirmed
§3's AppxManifest.xml skeleton declared <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" .../> while §10's failure-modes table separately, correctly, noted that Wide310x150Logo.png was not among the confirmed-committed Tauri-generated icon assets — a genuine self-contradiction between the "API contract" and the task's own risk disclosure, and one that would have made AC #2 (icon references resolve) fail on the very manifest this task ships. Resolved: removed the Wide310x150Logo attribute from <uap:DefaultTile>, kept the confirmed Square310x310Logo attribute (schema-valid minimum), and added an inline XML comment explaining the omission; §3, §11.
ISS-002 — Self-managed signing step assumed the EV certificate was already present in the runner's certificate store
The original §3 sign step referenced MSSTORE_EV_CERT_THUMBPRINT directly against signtool.exe with no step showing how that certificate gets into the windows-2022 runner's certificate store — a real implementer following the skeleton literally would hit "No certificates were found that met all the given criteria" on the first self-managed-signing CI run. This is the Windows-side analogue of TASK-APP-003's ISS-002 (macOS keychain import) and was an equally real gap. Resolved: added an explicit "Import EV signing certificate" step that decodes a base64-encoded PFX secret and imports it via Import-PfxCertificate before the sign step runs; §3, and a corresponding failure-mode row; §10.
ISS-003 — makeappx.exe/signtool.exe paths were hardcoded to one specific Windows SDK version string
The original CI skeleton hardcoded ...\bin\10.0.22621.0\x64\makeappx.exe and the same version for signtool.exe. Windows SDK versions installed on GitHub-hosted windows-2022 runner images change across image updates outside CyberOS's control; a hardcoded version string is a latent, silent future breakage. Resolved: replaced with a "Locate Windows SDK tools" step that discovers the installed SDK bin directory dynamically via Get-ChildItem/regex + Sort-Object -Descending (newest wins), asserts the resolved makeappx.exe actually exists, and passes both tool paths to later steps via $GITHUB_OUTPUT; §3, and the corresponding failure-mode row was rewritten to describe the now-narrower residual risk (no SDK at all, rather than wrong-version SDK); §10.
ISS-004 — tools/msix-identity-lint.sh, which gates AC #3, was referenced but never specified
AC #3 and §5's verification block both depend on a script (tools/msix-identity-lint.sh) whose actual logic was never shown anywhere in the spec — unlike the Microsoft Store Submission API flow (correctly left to WORKER-phase implementation per §11, since it's an external, versioned, well-documented contract), this script is CyberOS-specific gating logic central to keeping the "inert by default" guarantee honest, and leaving it unspecified was a real gap, not a legitimate scope boundary. Resolved: added the full script to §6, including the MSSTORE_RELEASE inertness check so the same lint used in local verification (AC #1, unset MSSTORE_RELEASE) doesn't false-fail; §6.
ISS-005 — MSSTORE_SIGNING_MODE was used in AC #5 and the CI skeleton but never declared as a config surface in §1
The signing-mode repo variable appeared first in §1 #5's prose and then directly in AC #5/§3's YAML without ever being named alongside MSSTORE_RELEASE as a first-class, independent gate a future maintainer needs to know about. Resolved: §1 #4 now explicitly names MSSTORE_SIGNING_MODE as a second, independent config surface and states it must not be conflated with MSSTORE_RELEASE; §1.
ISS-006 — §2's "wrap the NSIS output" framing was ambiguous about which artifact gets staged into the MSIX layout
As originally worded, "MSIX-wrap the NSIS output" could be misread as "unpack the NSIS .exe installer's contents" rather than the actual mechanism (stage the raw cargo/tauri build binary directly, using the same build profile NSIS also consumes). This ambiguity could have led a WORKER-phase implementer to attempt extracting files from the NSIS installer rather than building from source, an unnecessary and fragile detour. Resolved: §2's rationale paragraph now states explicitly that the MSIX layout is staged from the raw compiled binary at target/release/<binary-name>.exe, never by extracting from the NSIS wrapper, and clarifies what "wrap the NSIS output" means in this task's context (reusing the same build invocation/profile, not the installer artifact itself); §2.
ISS-007 — related_tasks references TASK-APP-005/TASK-APP-006, which don't exist on disk yet, with no forward-reference disclosure
Same category of finding as TASK-APP-003's ISS-006: this task's siblings from the same approved 5-task PLAN hadn't been authored yet at the time this task was written. depends_on/blocks are both empty (the fields the repo's placeholder-annotation rule actually scopes to), so no inline placeholder comment is mechanically required, but leaving the forward reference undisclosed in §9 would strand a future reader. Resolved: added an explicit §9 note documenting the deliberate same-batch forward reference, matching the disclosure pattern TASK-APP-003 §9 used; §9.
§3 — Resolution
All 7 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-004 audit.