Collapse the edition split — one target, twins merged, verify-editions retired

Phase 1 of the 2026-07-30 one-app pivot (DESIGN 0bec9a6, card c3a3ddd5):
the KanbanPro target, LaneworkPro scheme, KanbanProTests module-alias
bundle, KanbanPro/ source root and scripts/verify-editions.sh retire
wholesale. project.yml reads as a single-target file again (anchors
inlined, header rewritten in tier vocabulary).

The edition twins merge: EditionTypes -> PasteboardTypes (one
UTType(exportedAs:) home — the one app owns the family types),
EditionAbout -> AboutBox (the quiet Pro signpost survives as the About
box's one line; "…in Settings" deferred until the StoreKit phase gives
it somewhere to point). InertGitTests drops its Base prefix — the
inert-.git posture is unconditional app behavior, unsubscribed and
lapsed being one state.

Entitlements gain com.apple.security.network.client, declared now and
dormant until Pro's remotes use it; no keychain access group. The App
Group key deliberately stays — it goes with AppGroup.swift in phase 2,
since pulling it first would silently drop the app into the fallback
container. README/RELEASE.md build-and-pipeline prose updated to the
one-record world; the subscription story lands with phase 3.

1893 tests in 322 suites green.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-30 17:25:50 -04:00
parent f7c8088783
commit 092300c7d2
52 changed files with 253 additions and 905 deletions
+16 -7
View File
@@ -1,6 +1,14 @@
import Foundation
import os
/// ** Retired by the one-app collapse one-app collapse phase 2.** 12-editions.md App-side
/// state (re-ruled 2026-07-30) removes the App Group wholesale: with one app there is no sibling
/// to share a container *with*, so the registry and its peers home in the ordinary sandbox
/// container, records carry one grant and one open-now flag, and this type goes away with the
/// entitlement. Everything below still describes the shipping code, and the code still works
/// it is simply describing a world that is being dismantled in a later phase, so the two-app
/// reasoning is left standing rather than half-rewritten into a fiction.
///
/// **The family App Group** where every edition's app-side state lives (12-editions.md
/// § Distribution, ruled 2026-07-29; 02-architecture.md § Per-board app state).
///
@@ -44,10 +52,11 @@ public enum AppGroup {
/// case and never a shipped app's.
public static let baseEditionID = "dev.rzen.indie.Kanban"
/// Pro's bundle id (12 Targets, ruled 2026-07-27). Named here as well as in
/// `ProEdition.bundleIdentifier` because *base* has to know it: the popover's awareness line and
/// the grant-slot keying are shared-tree code that must be able to name the other edition
/// without compiling any of it.
/// The bundle id the retired Pro *app* would have carried (12 Targets, ruled 2026-07-27)
/// **one-app collapse phase 2**: no app has ever shipped under it, and nothing will now that Pro
/// is a subscription rather than a second binary (12 Distribution, re-ruled 2026-07-30). It
/// stays only because the awareness line and the grant-slot keying still read it; both go in the
/// same phase, and this constant with them.
public static let proEditionID = "dev.rzen.indie.KanbanPro"
/// Which edition is running the key every per-edition slot on a shared record is stored under.
@@ -128,12 +137,12 @@ public enum AppGroup {
///
/// ### Why the app has to know
///
/// The unit-test host **is the app** (`KanbanTests` and `KanbanProTests` are hosted bundles), so
/// The unit-test host **is the app** (`KanbanTests` is a hosted bundle), so
/// `KanbanApp.init()` runs for real on every test launch and builds an `AppModel` over whatever
/// the defaults resolve to. Every *object* a test constructs takes its storage by injection that
/// is the seam, and it is untouched but the host's own launch has no injection point, and after
/// the 2026-07-29 ruling the thing it would reach for is a container shared with the sibling
/// edition and with the developer's running copy. Its launch sweep would collect real staged
/// the 2026-07-29 ruling the thing it would reach for is a container shared with the developer's
/// own running copy. Its launch sweep would collect real staged
/// clipboard trees; its `refreshRecents()` would resolve, refresh and rewrite real records.
///
/// So the *default* moves for a test host, which is the one place a default can be wrong in a way