Home app-side state in the shared App Group container
Every edition declares group.dev.rzen.indie.Kanban and homes its
app-side state there from day one (12-editions.md ruling 2026-07-29):
- AppGroup namespace: container resolution with per-edition fallback
when unprovisioned, shared UserDefaults suite, edition identity, and
a unit-test-host redirect (the test host IS the app — its launch
sweep and recents refresh must not touch the real shared container).
- BoardRecord: bookmark/isOpenNow replaced by per-edition grants and
openNow keyed by bundle id; hand-written Codable keeps legacy keys
decoding (adopted in memory as the running edition's slots, upgraded
on first save); every other field stays common.
- RecentBoard gains needsReopen: no grant of ours but somebody's —
first click runs an open panel pre-anchored at the recorded path,
prompt "Grant"; recordOpen mints this edition's slot onto the
matched shared record (path fallback only after identity fails and
only against records holding no grant of ours, so re-granting never
forks the record).
- Cross-edition freshness: stat-cheap mtime+size stamp re-reads the
registry when the sibling edition wrote it, so one edition's save
never erases the other's records wholesale.
- restorables() filters on this edition's open-now flags; the board
popover gains BoardEditionPresence ("Also open in Lanework Pro"),
pid-liveness-checked so crash residue never lies.
- Clipboard staging store moves to the group container; the sweep
claims doomed trees by atomic rename into .sweeping/ then deletes,
so the sibling's concurrent sweep is a non-event.
- Template store re-homed to the group container per the 09-templates
re-ruling; scalars (quick-style recents, window size) move to the
shared suite.
- verify-editions.sh: 30 checks (each edition carries exactly the
family group). No pathfinder 1.x migrator: 1.x predates the
registry; state starts fresh in the group container.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -144,7 +144,8 @@ fi
|
||||
|
||||
for key in com.apple.security.app-sandbox \
|
||||
com.apple.security.files.user-selected.read-write \
|
||||
com.apple.security.files.bookmarks.app-scope; do
|
||||
com.apple.security.files.bookmarks.app-scope \
|
||||
com.apple.security.application-groups; do
|
||||
if has_entitlement "$BASE_APP" "$key"; then
|
||||
pass "base carries $key"
|
||||
else
|
||||
@@ -164,7 +165,7 @@ done
|
||||
# archive — which is why they are named out loud below rather
|
||||
# than quietly filtered.
|
||||
toolchain_injected='^(com\.apple\.security\.get-task-allow|com\.apple\.application-identifier|com\.apple\.developer\.team-identifier|com\.apple\.security\.temporary-exception\.[a-z.-]*)$'
|
||||
settled_minimum='^(com\.apple\.security\.app-sandbox|com\.apple\.security\.files\.user-selected\.read-write|com\.apple\.security\.files\.bookmarks\.app-scope)$'
|
||||
settled_minimum='^(com\.apple\.security\.app-sandbox|com\.apple\.security\.files\.user-selected\.read-write|com\.apple\.security\.files\.bookmarks\.app-scope|com\.apple\.security\.application-groups)$'
|
||||
|
||||
base_extra="$(entitlement_keys "$BASE_APP" | grep -vE "$settled_minimum" | grep -vE "$toolchain_injected")"
|
||||
if [ -n "$base_extra" ]; then
|
||||
@@ -187,6 +188,7 @@ head2 "Pro — entitlements"
|
||||
for key in com.apple.security.app-sandbox \
|
||||
com.apple.security.files.user-selected.read-write \
|
||||
com.apple.security.files.bookmarks.app-scope \
|
||||
com.apple.security.application-groups \
|
||||
com.apple.security.network.client \
|
||||
keychain-access-groups; do
|
||||
if has_entitlement "$PRO_APP" "$key"; then
|
||||
@@ -196,6 +198,45 @@ for key in com.apple.security.app-sandbox \
|
||||
fi
|
||||
done
|
||||
|
||||
# ------------------------------------------------------------------------------ the App Group
|
||||
#
|
||||
# 12-editions.md ▸ Distribution (ruled 2026-07-29): "every edition declares the family group —
|
||||
# `group.dev.rzen.indie.Kanban` — and the board registry with its Application Support peers homes in
|
||||
# the group container **from day one**". The upgrade story is exactly this string matching across the
|
||||
# two signatures: an edition declaring a group of its own would share nothing while looking as though
|
||||
# it did, and nothing in the app could detect the difference — the container would simply be empty.
|
||||
|
||||
head2 "The App Group — one container, either app"
|
||||
|
||||
# The group ids the signature declares, one per line. Read from the signature rather than the source
|
||||
# `.entitlements` for the reason every check here does: the shipped binary is the claim.
|
||||
#
|
||||
# `sed` over the whole stream rather than a line-oriented pass, because `codesign --xml` emits the
|
||||
# entire plist on **one line** — which is also why `entitlement_keys` above reaches for `grep -o`
|
||||
# instead of PlistBuddy. The two substitutions trim everything before this key's `<array>` and
|
||||
# everything after its close, leaving only its own `<string>`s to unwrap.
|
||||
app_groups() {
|
||||
entitlements_of "$1" |
|
||||
sed -e 's/.*<key>com\.apple\.security\.application-groups<\/key><array>//' \
|
||||
-e 's/<\/array>.*//' |
|
||||
grep -o '<string>[^<]*</string>' |
|
||||
sed 's/<[^>]*>//g'
|
||||
}
|
||||
|
||||
expected_group="group.dev.rzen.indie.Kanban"
|
||||
base_groups="$(app_groups "$BASE_APP")"
|
||||
pro_groups="$(app_groups "$PRO_APP")"
|
||||
|
||||
for app_label in "base:$base_groups" "pro:$pro_groups"; do
|
||||
label="${app_label%%:*}"
|
||||
groups="${app_label#*:}"
|
||||
if [ "$groups" = "$expected_group" ]; then
|
||||
pass "$label declares exactly $expected_group"
|
||||
else
|
||||
fail "$label declares '$groups', expected '$expected_group'"
|
||||
fi
|
||||
done
|
||||
|
||||
# ------------------------------------------------------------------------------ bundle identity
|
||||
|
||||
head2 "Bundle identity"
|
||||
|
||||
Reference in New Issue
Block a user