Contracts: three apps
The fixed interfaces for the split: the package graph, what moves where, apps.json, bundle IDs, CI behaviour and who owns which files. Change this page only through the conductor.
1. Package graph
There are no cycles. An arrow means "depends on".
PTCore → (nothing)
SGitVault → (nothing)
PTWeb → PTCore
PTAgenda → PTCore, PTWeb
PTNews → PTCore, PTWeb
PTVaultUI → PTCore, SGitVault
PTAsk → PTCore, PTAgenda, PTNews, PTVaultUI
PTLanes → PTCore, PTVaultUI
Each package is a local Swift package: swift-tools-version 6.0, platform .iOS(.v18), one library product named after the package, and a Tests/<Name>Tests target. If a move shows that a package needs something above it, move that piece down (usually into PTCore), or inject it with a closure or protocol. Never add an upward edge. If neither works, write it in your status page and the conductor decides.
2. What moves where
From PrompTugal/ | To | Owner |
|---|---|---|
Design/ | PTCore | platform (phase 2) |
App/HTTPClient.swift, the log types, Lisbon time, Content.swift shared constants, BuildInfo | PTCore | platform (phase 2) |
Debug/: the log stores and the panel building blocks | PTCore | platform (phase 2) |
Debug/: screens that show team or vault data | stay in the Team app | design |
Bridge/, Shell/WebPage.swift, App/Web/ (Websites.json, WebsiteView) | PTWeb | team (WebPage and WebsiteView: design reviews) |
Schedule/, Notifications/, Shared/NowNext.swift | PTAgenda | team |
Store/ | stays in the Team app (Reading credit is hidden in Release; Dinis has not decided) | team |
News/, the graph (Dashboards/Graph*) | PTNews | design |
AI/ | PTAsk | collab |
Lanes/ | PTLanes | collab |
Coordination/ | stays in the Team app | collab |
Vault/ | PTVaultUI | vault |
Team/, Dashboards/ (except the graph), Shell/ (except WebPage) | the Team app (Apps/Team/) | design and team |
App/ (the remaining parts) | the Team app | platform |
Tests move with their code into the package's Tests/ when they need no app host. Tests that need the app host stay in the Team app's test target.
3. Bundle IDs and records
| App | Bundle | Extensions | App Group | Upload |
|---|---|---|---|---|
| PrompTugal | news.promptugal.reader (existing record "PrompTugal.news") | .widgets, .intents | group.news.promptugal.reader | yes |
| Team | news.promptugal.team (new record, Dinis creates it) | none tonight | group.news.promptugal.team if needed | yes, once the record exists |
| RiskMandate | ai.riskmandate.app | none | none | no (blocked on the publishing account) |
Each app has its own MARKETING_VERSION. PrompTugal continues from 0.3.1, Team starts at 0.1.0 and RiskMandate at 0.0.1. CI sets build numbers per app: run number plus that app's offset, so PrompTugal stays above the manual build 6.
Each app has its own entitlements, Info settings and PrivacyInfo.xcprivacy, and asks only for what it uses. The PrompTugal app ships no debug panels in Release, no team screens and no lanes.
4. apps.json
{
"packages": {
"PTCore": {"path": "Packages/PTCore", "deps": []},
"SGitVault": {"path": "Packages/SGitVault", "deps": []},
"PTWeb": {"path": "Packages/PTWeb", "deps": ["PTCore"]}
},
"apps": {
"team": {"project": "Apps/Team/Team.xcodeproj", "scheme": "Team", "bundle": "news.promptugal.team",
"paths": ["Apps/Team/"], "packages": ["PTLanes", "PTAsk"], "testflight": true},
"promptugal": {"project": "Apps/PrompTugal/PrompTugal.xcodeproj", "scheme": "PrompTugal", "bundle": "news.promptugal.reader",
"paths": ["Apps/PrompTugal/"], "packages": ["PTAsk", "PTAgenda"], "testflight": true},
"riskmandate": {"project": "Apps/RiskMandate/RiskMandate.xcodeproj", "scheme": "RiskMandate", "bundle": "ai.riskmandate.app",
"paths": ["Apps/RiskMandate/"], "packages": ["PTCore"], "testflight": false}
},
"all": [".github/workflows/ios.yml", ".github/workflows/ios-release.yml", "apps.json", "scripts/release/", "Config/Shared/"]
}
Rules:
packageson an app lists the direct packages. Tools compute the closure throughdeps.- A changed file triggers an app when it is under one of the app's
paths, or under thepathof any package in its closure. A changed file underalltriggers every app. - A test (
scripts/apps/check_apps.py, run in CI on Linux) fails if: - a project's
XCLocalSwiftPackageReferencelist differs from its app's closure; - a package's
Package.swiftdependencies differ from itsdeps; - the graph has a cycle.
scripts/apps/affected.py <base> <head>prints the affected apps as JSON, for the CI matrix.
5. CI
ios.yml: 1. Achangesjob on ubuntu runsaffected.py. 2. A matrix over the affected apps builds Debug and Release and runs the unit tests on iPhone and iPad. 3. A matrix over the affected packages runsswift test/xcodebuild testfor the packages.ios-release.yml:- It uses the same
changesjob. - For each affected app with
testflight: trueand a green build, it packages, signs and uploads to that app's record. - Each app has its own variable to pause pushes:
TESTFLIGHT_ON_PUSH_<APP>. The existingTESTFLIGHT_ON_PUSH=falsestill pauses all of them. - Tags
team-v*,promptugal-v*andriskmandate-v*release one app. - Manual runs take an
appinput (allor one app). screens.ymlphotographs the PrompTugal app and the Team app separately.website.ymlis unchanged: site and docs only.
6. Ownership tonight
| Role | Owns | Branch |
|---|---|---|
| platform | the workspace, every .xcodeproj, Config/, apps.json, scripts/apps/, PTCore, Apps/*/App/ (entry points) | split/platform |
| team | PTWeb, PTAgenda, Store/, Team/ | split/team |
| design | PTNews, Shell/ and Dashboards/ in the Team app, the PrompTugal app's screens and Destinations, Debug/ screens | split/design |
| collab | PTAsk, PTLanes, Coordination/ | split/collab |
| vault | PTVaultUI, SGitVault | split/vault |
| release | .github/workflows/ios*.yml, .github/workflows/screens.yml, scripts/release/ | split/release |
| editor | website/, briefs/ (except other sessions' status pages) | split/editor |
The conductor merges into dev once a branch is green and its phase is due.
7. Rulings during the night
These replace the matching rows above.
- PTCore also gets:
- from platform (phase 2):
Event,Story,FeedClient(withsite),RemoteFeed,LoadPhase,FeedCache, andenum Wire {}with its loose decoders; - from collab: one file,
CollabSeams.swift, with protocols and value types only (Data, String, URL; no vault types). The conformance,VaultCollabAccess, lives in PTVaultUI; - from design (phase 4): the shared shell UI parts, in its own subfolder
Sources/PTCore/Shell/. - PTAgenda also gets
News/AgendaView.swiftandApp/ReaderStore.swift, moved by team. ReaderStore has the schedule injected; it no longer usesTeamFeatures.shared. - PTNews:
- There is no
Dashboards/Graph*file; the graph is already inNews/. - PTNews defines a
NewsSourceprotocol andNewsFeeds(newsletter, board, graph). - Each app adds
extension ReaderStore: NewsSource {}. DashboardDataforwards toNewsFeeds.- The bridge works at JSON level. ScheduleDocument, ScheduleMerge and ScheduleID stay in PTAgenda.
- Seams between packages use small protocols and registries, set by each app at launch:
- in PTWeb:
WebBridgeHub,BridgeServices; - in PTAgenda:
AgendaTeamSource,AgendaIdentitySource,VaultPicksAccess,AgendaHub.
The public app supplies team sources that return nothing.
- PTAsk takes an
AskContextProvider.PublicAskProvider, in PTAsk, offers the agenda, picks, news and the current page.TeamAskProvider, in the Team app (Coordination/), keeps every current team context and tool. - App Intents, widgets and the intents extension go with the PrompTugal app in phase 4. The Team app has no extensions tonight.
- URL schemes: PrompTugal uses
promptugal://, Team usesptteam://. Reminder links take the scheme from the app. - Background refresh:
VaultBackground.register(taskIdentifier:)takes each app's BGTask id from its entry point. - Keychain after the split: vault keys saved by today's app (
news.promptugal.reader) stay with the PrompTugal app. In the Team app (news.promptugal.team), teammates import their key again. - One version for every app (Dinis, 01:10 UTC):
- The app version of all three apps equals the repo release version that CI tags for that commit: v0.4.n → 0.4.n. This replaces the per-app versions in §3.
- The series jumps to v0.4.0, which is above the 0.3.1 already in App Store Connect.
- iOS release builds the release commit, so the build and the tag match.
- Build numbers still go up with every upload.
- Runner load: Screens runs by hand only during the split.
- RiskMandate publishes from the same Apple account (Dinis, 01:25 UTC). It uses bundle
ai.riskmandate.appand getstestflight: trueonce it builds. Uploads skip cleanly until Dinis creates its App Store Connect record, as for Team. - Internal testers only during the summit (Dinis, 01:30 UTC). Every build goes to each app's internal TestFlight group, and internal testers get it as soon as Apple finishes processing. There are no external groups and no Beta App Review submissions, so the per-push version numbers cost nothing. External testing and App Review come after the summit.
8. Next round: app-first, three workstreams (conductor.B, 8 Nov, 10:20 UTC)
Dinis chose three parallel workstreams from the app-first brief. GitHub's standard macOS runners only.
| Workstream | Owner | Where | Package graph |
|---|---|---|---|
PTSync: manifest delta sync of promptugal.news/api/v1/manifest.json, packs, SHA-256 check, atomic switch, build seed, offline outbox | collab | Packages/PTSync | PTSync → PTCore only |
| Decks and presenter mode in the PrompTugal app (investor decks first, four languages, offline, external display) | deck (new session) | Packages/PTDeck, screens wired in Apps/PrompTugal/Shell/ through design | PTDeck → PTCore only. Deck data comes through an injected loader, so PTDeck does not wait for PTSync. |
| Native stand-ups, command centre, reports and pulse in the Team app | team | Apps/Team/Sources/Team/ | uses existing packages only |
Rules for this round:
- New package entries: the owner of a new package adds it to
apps.json(packages, and the app'spackageslist) in the same commit that creates the package.check_apps.pymust pass. Platform reviews; nobody else edits another app's project. - No upward edges. A package that needs PTSync data takes a closure or protocol; the app wires PTSync in at launch.
- CI: quick runs (
quick=true) for fix rounds, one full run for READY,runner=macos-26. Cancel your own red or superseded runs at once. - Shell entries: new screens in the PrompTugal app are registered by design in
Apps/PrompTugal/Shell/; ask design with the screen id and view. - Merge order: whichever is green first. The conductor merges the exact green commit, as before.