diff --git a/.agents/learnings/promoted/2026-08-30-ios-extension-version-verification.md b/.agents/learnings/promoted/2026-08-30-ios-extension-version-verification.md
new file mode 100644
index 0000000..d8497c0
--- /dev/null
+++ b/.agents/learnings/promoted/2026-08-30-ios-extension-version-verification.md
@@ -0,0 +1,39 @@
+---
+id: 2026-08-30-ios-extension-version-verification
+date: 2026-08-30
+status: promoted
+scope: iOS release artifact verification
+evidence: build 45 Xcode archive warning plus direct PlistBuddy inspection of the exported IPA
+destination: .codex/skills/release-mobile/SKILL.md
+review_after: 2027-02-28
+---
+
+## Symptom
+
+The signed iOS build logged that the widget extension build number 44 did not
+match the containing app build number 45, even though export completed.
+
+## Reproduction
+
+Build iOS version 1.0.6 with EAS auto-increment from build 44 to 45 and retain the
+archive log. Extract the resulting IPA with `ditto`, then read the containing app
+and every packaged extension `Info.plist` with `PlistBuddy`.
+
+## Cause
+
+The archive warning described an intermediate state and was not authoritative
+for the exported package. Direct IPA inspection showed both the app and
+`LiveDrawWidget.appex` at version 1.0.6 build 45.
+
+## Remediation
+
+For every iOS release, inspect version and build values in the exported app and
+all packaged extensions. Reject or rebuild only when the final IPA values differ;
+do not accept or reject a release from archive warnings alone.
+
+## Evidence
+
+`PlistBuddy` read `CFBundleShortVersionString` 1.0.6 and `CFBundleVersion` 45 from
+both packaged plists. `codesign --verify --deep --strict` passed for the app, and
+the verified IPA SHA-256 was
+`9d4779bddac4647911a3a38e4ee37cf794c2b8691e0746a041b629c62b308e7f`.
diff --git a/.codex/skills/release-mobile/SKILL.md b/.codex/skills/release-mobile/SKILL.md
index bb40cd8..ba7e23c 100644
--- a/.codex/skills/release-mobile/SKILL.md
+++ b/.codex/skills/release-mobile/SKILL.md
@@ -54,5 +54,9 @@ additional TestFlight distribution requires another fresh authorization.
Preserve platform signing and local EAS versioning. After a build, verify that
tracked changes are limited to the expected version metadata, then independently
-read the artifact version and signature. Report the artifact, version/build
-number, upload result when authorized, and store read-back state.
+read the artifact version and signature. For an iOS IPA, inspect every packaged
+app extension as well as the containing app and require matching
+`CFBundleShortVersionString` and `CFBundleVersion` values. Treat Xcode archive
+warnings as diagnostic evidence, not the final artifact state: decide whether to
+reject or rebuild only after reading the exported IPA itself. Report the artifact,
+version/build number, upload result when authorized, and store read-back state.
diff --git a/AGENTS.md b/AGENTS.md
index 8355dd9..507994e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -36,6 +36,7 @@ MCP service in one npm/Turborepo workspace.
mechanical one-line edit whose coordination cost exceeds the work.
11. Use high-effort reviewers only for the risk triggers in
`docs/agents/routing.md`.
+12. Agents must not use em dash characters in text they add to this repository.
## Repository map
diff --git a/apps/mobile/app.json b/apps/mobile/app.json
index 49b2ebc..123ada7 100644
--- a/apps/mobile/app.json
+++ b/apps/mobile/app.json
@@ -24,7 +24,7 @@
"group.com.sauci.app"
]
},
- "buildNumber": "44",
+ "buildNumber": "45",
"usesAppleSignIn": true,
"googleServicesFile": "./GoogleService-Info.plist",
"infoPlist": {
diff --git a/apps/mobile/ios/Sauci/Info.plist b/apps/mobile/ios/Sauci/Info.plist
index 875de15..78a8d59 100644
--- a/apps/mobile/ios/Sauci/Info.plist
+++ b/apps/mobile/ios/Sauci/Info.plist
@@ -47,7 +47,7 @@
CFBundleVersion
- 44
+ 45
GOOGLE_ANALYTICS_INAPURCHASE_COLLECTION_ENABLED
ITSAppUsesNonExemptEncryption
diff --git a/apps/mobile/targets/widget/Info.plist b/apps/mobile/targets/widget/Info.plist
index c7c5fea..059cefe 100644
--- a/apps/mobile/targets/widget/Info.plist
+++ b/apps/mobile/targets/widget/Info.plist
@@ -8,7 +8,7 @@
com.apple.widgetkit-extension
CFBundleVersion
- 44
+ 45
CFBundleShortVersionString
1.0.6
diff --git a/scripts/lint-harness.mjs b/scripts/lint-harness.mjs
index 3611d07..ed8a046 100644
--- a/scripts/lint-harness.mjs
+++ b/scripts/lint-harness.mjs
@@ -40,6 +40,13 @@ for (const file of allFiles) {
}
if (!scoped) {
+ const agentsFile = path.join(root, 'AGENTS.md');
+ const agents = await readFile(agentsFile, 'utf8');
+ const noEmDashRule = 'Agents must not use em dash characters in text they add to this repository.';
+ if (!agents.includes(noEmDashRule)) {
+ fail(agentsFile, 'agent writing rule against em dash characters is missing', `restore: ${noEmDashRule}`);
+ }
+
const packageJson = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
const codexConfigFile = path.join(root, '.codex', 'config.toml');
const codexConfig = await readFile(codexConfigFile, 'utf8');