Privacy Policy

Draft · not published · no effective date · not a policy in force

Draft. This is not a published privacy policy, and nothing on it is a promise.

Plumb has no privacy policy. The app's Privacy Policy link still points at https://plumb.example.com/privacy — a placeholder, described as one in the source — so there is no approved policy copy anywhere in this repository to reproduce here.

What follows is the scaffolding for one: the sections a Plumb policy will need, and beside each the product fact — what the current build actually does with data, read out of the app's own source and cited to the file. None of the wording here has been written or approved as privacy copy. None of it binds ZIX DEV Inc. to anything, and none of it is a representation to a user. The clauses are written and approved on their own row, before this page is published.

A privacy page carries a specific risk that a terms page does not: a description of how software behaves reads as a commitment about how it will behave. So it is worth being exact about what the facts below are. They are observations of one build, at one commit — the tree this page was written against — not undertakings about future versions. Anything in them that should become a promise has to be restated as a clause by someone entitled to make one.

Today's link: PlumbLinks.privacyPolicy in Sources/PlumbUI/Localization.swift.

What the policy will cover

Twelve sections. Every clause is pending; every product fact is cited. Where the app settles nothing — section 11 is the clearest case — the fact block says so rather than filling the space.

  1. Who is responsible for your data
  2. What Plumb records
  3. Where your data is kept
  4. What leaves your device
  5. Subscriptions, and what Apple sees
  6. The install identifier
  7. Diagnostics and analytics
  8. Face ID and device permissions
  9. Exporting your ledger
  10. Deleting your data
  11. Children
  12. Your rights, changes and contact

1. Who is responsible for your dataClause pending

What the app does today

Plumb is published by ZIX DEV Inc. and distributed as an iPhone app under the bundle identifier app.zixdev.plumb. There is no Plumb account, no sign-up, no password and no server-side profile. Plumb operates no backend of its own, so there is nothing on our side holding a copy of your ledger.

ExportOptions.plist, Sources/PlumbStore/SQLiteStore.swift

Which legal entity is the controller, in which jurisdiction, and under which regime is a legal determination and is not made here.

2. What Plumb recordsClause pending

What the app does today

Everything Plumb holds is something you typed into it. The database has thirteen tables; these are the ones carrying anything about you:

  • Accounts — a name, an institution name, a kind, a balance, an optional credit limit, an optional note, a currency, and an optional last 4 you may enter and may leave blank.
  • Transactions — a date, a merchant name, an amount, a category, an account, an optional note, and flags for recurring, excluded and reconciliation adjustments.
  • Categories and budgets — your labels and what you planned to spend against each, per cycle.
  • Goals — a name, a target, what is saved and a monthly rate.
  • Recurring rules — a merchant, an amount and a cadence.
  • Budget cycle — your income figure, its frequency and the day your cycle starts.

Plumb connects to no bank and asks for no banking credential, so none of this arrives from anywhere but your own typing.

Sources/PlumbStore/Schema.swift, Sources/PlumbKit/Models.swift

Setup says it in the app's own words:

Nothing leaves your phone. No credentials, ever.

copy.setupPrivacyNote in Sources/PlumbUI/Resources/en.lproj/Localizable.strings. Shipped app copy, quoted unchanged.

The remaining six hold no financial detail about you: the diagnostics log and the reconciliation log (section 7), the install identifier (section 6), a cached subscription tier (section 5), whether guided setup has been completed, and the currency list. Seven and six is the thirteen — no table is unaccounted for.

3. Where your data is keptClause pending

What the app does today

In one SQLite file on your iPhone, inside the app's own container: Application Support/app.zixdev.plumb/plumb.sqlite, with its two write-ahead-log sidecars. There is no iCloud sync, no CloudKit container and no second copy anywhere.

The file is marked NSFileProtectionCompleteUntilFirstUserAuthentication — readable only after the device has been unlocked once since it booted. The source records this as a deliberate relaxation from complete protection, because a SQLite database with open WAL sidecars fails I/O seconds after the screen locks; tightening it is noted there as its own product decision.

Your interface preferences — hide balances, app lock, language, appearance — are stored separately in the app's own UserDefaults, and carry no figures.

SQLiteStore.Location and SQLiteStore.applyFileProtection in Sources/PlumbStore/SQLiteStore.swift; Sources/PlumbUI/AppSettings.swift

Because the database sits in the app container, an encrypted iPhone backup — iCloud or Finder — includes it. What that means for a published policy is a clause, not a fact this page settles.

4. What leaves your deviceClause pending

What the app does today

Nothing, other than what you export yourself (section 9) and what Apple handles for a purchase (section 5). Plumb has no network layer at all — not a disabled one, not an unused one. There is no analytics SDK, no crash reporter, no advertising identifier and no tracking framework linked into the app.

This is not a claim resting on a comment. A test walks every Swift file under Sources and App, skips comment lines, and fails the build if URLSession, URLRequest, NWConnection, CFStream or Network. appears in code. A second test proves the guard fires rather than passing vacuously.

A scan of source cannot see a framework that arrives already compiled, so nothing is linked in is a separate check from nothing is called, and it is made at both doors a dependency can enter by. Package.swift declares one dependency, GRDB, and no binary target; the Xcode project's packageReferences holds only the local package that resolves to it. There is no second door and no prebuilt framework behind either.

testTheSourcesContainNoNetworkingSymbols and testThePrivacyGuardActuallyFires in Tests/PlumbKitTests/ReconcilePersistenceTests.swift; Package.swift and packageReferences in Plumb.xcodeproj for the linkage. GRDB links the OS's SQLite and vendors no cryptography

A policy still has to state what happens if that ever changes, and by what notice. That is the clause, and the guarantee above is a property of one build, not a commitment this page can make on behalf of later ones.

5. Subscriptions, and what Apple seesClause pending

What the app does today

Plumb sells subscriptions as Apple in-app purchases through StoreKit. Payment, renewal and cancellation happen between you and Apple against your Apple ID; Plumb never sees a card number, a billing address or your Apple ID, and holds no billing record of its own.

What Plumb keeps locally is a one-row cache of the answer StoreKit gave: a tier, a product identifier and when it was last checked. It is described in the schema as a cache and not an authority — StoreKit is re-read every launch. Apple's own handling of the transaction is governed by Apple's privacy policy, not this one.

entitlement_snapshot in Sources/PlumbStore/Schema.swift; Sources/PlumbBilling/

6. The install identifierClause pending

What the app does today

Plumb generates one random UUID the first time the database is opened and stores it in the database's own app_meta table. It is not the advertising identifier, not the device's vendor identifier, and it is not kept in the Keychain — the source notes that v1 touches no credentials class — so deleting and reinstalling the app yields a new one.

It never leaves the device, because nothing does (section 4).

SQLiteStore.deviceID and SQLiteStore.resolveDeviceID in Sources/PlumbStore/SQLiteStore.swift; Schema.Meta.deviceID

A policy names an identifier like this one explicitly even when it is local, because a reader cannot tell from the outside that it is. Whether it needs to exist at all in a build with no sync is a product question worth asking before the policy is drafted around it.

7. Diagnostics and analyticsClause pending

What the app does today

There is no analytics. Plumb keeps a local diagnostics log — instrumentation_events — of four columns: a row id, an event name, a day, and an optional text detail. It has no amount column, which the schema calls out as the point: it is money-free by construction. It is read only by the app itself and is deleted by a reset.

The reconciliation log beside it is held to the same bar by a test that walks each signal's fields and fails if any is declared as a Decimal — so a balance or an adjustment amount cannot be added to one without the suite noticing.

A separate launch-timing probe exists for development. It is gated entirely on the -plumbMeasureLaunch 1 launch argument, writes one JSON line into the app's own container, and does not run in an App Store build.

instrumentation_events and reconcile_log in Sources/PlumbStore/Schema.swift; assertCarriesNoMoney in Tests/PlumbKitTests/ReconcilePersistenceTests.swift; Sources/PlumbUI/LaunchProbe.swift

8. Face ID and device permissionsClause pending

What the app does today

Plumb requests exactly one system permission, and only if you switch the app lock on in Settings. The app declares one usage description and no others — no camera, no contacts, no location, no notifications, no photo library, no tracking prompt:

Plumb asks for Face ID to unlock the app when you switch the lock on in Settings.

INFOPLIST_KEY_NSFaceIDUsageDescription in Plumb.xcodeproj. Shipped, quoted unchanged.

The lock is a screen gate and the source is explicit that it is one: authentication happens inside iOS through LocalAuthentication, no biometric data reaches Plumb, and the lock does not encrypt the database. It accepts the device passcode as well as biometrics, so a phone with no enrolled face is not locked out of its own money.

Sources/PlumbUI/AppLock.swift

9. Exporting your ledgerClause pending

What the app does today

Settings offers Export CSV. It writes your ledger — date, merchant, amount, category, account, and the recurring, excluded, adjustment and note fields — to a destination you choose through the system share sheet.

The file carries real figures. The source states why: hide balances is a shoulder-surfing setting for a screen, and masking a file you asked for would hand you a column of bullets. This is the one path by which your data leaves the device, and it runs only when you start it. Where the file goes afterwards is outside Plumb entirely.

LedgerExport.header in Sources/PlumbUI/LedgerExport.swift

10. Deleting your dataClause pending

What the app does today

Two routes, and both are complete because there is no server copy to reach for.

  • Settings → Reset data. A hard delete of eleven tables — accounts, categories, budgets, transactions, goals, recurring rules, the cycle, both logs, onboarding state and your currency selection — plus the reconciliation prompt keys. The source names it the only hard delete in the app, on the ground that a user who asked for the data to be gone should not get a tombstone. Your subscription is deliberately not cleared, so a reset cannot silently downgrade someone who is paying.
  • Deleting the app. iOS removes the container, and the database and both sidecars go with it.

SQLiteStore.purgeForColdStart in Sources/PlumbStore/SQLiteStore.swift; AppModel.resetToColdStart in Sources/PlumbUI/AppModel.swift

Note the wording difference the clause will have to resolve: the app's Settings row is Reset data, and the store method behind it is documented against Re-run guided setup. A policy that names a control has to name the one the user actually sees.

11. ChildrenClause pending

What the app does today

Nothing in the app determines this. Plumb collects no age, has no account and therefore no age gate, and this repository carries no age rating or kids-category setting — those live in App Store Connect, which is not in this tree.

Like section 9 of the Terms draft, this section has no product fact to build on. It is a policy decision first and a clause second.

12. Your rights, changes and contactClause pending

What the app does today

Access, portability and erasure are all answered by the device itself: your data is on your phone, export is in Settings, and so is deletion. There is nothing to request from us, because we hold nothing to return.

No contact address, data-protection contact, jurisdiction or supervisory authority is published on this site, and none is invented here.

Which regimes apply, what rights they confer beyond what the app already gives, how a request would be made if one were ever needed, and how you are told when the policy changes — all clause, none of it settled by the product.

The gap this page is naming

No approved privacy copy exists in this repository or in the evidence linked to the row that commissioned this page. The page was built to hold approved copy, not to stand in for it.

  • The app's privacy link is a placeholder. PlumbLinks.privacyPolicy points at https://plumb.example.com/privacy, annotated in the source as a placeholder kept as one constant so release prep finds it in one grep. It is reached from the plan sheet — where App Review expects a working privacy policy — so this is a submission blocker, not a tidy-up.
  • There is no privacy manifest in the repository. No PrivacyInfo.xcprivacy exists anywhere in the tree. Whether Plumb needs one, and what it must declare, is an App Store packaging question for the app's technical lead — not a marketing-site question, and not settled by this page.
  • Six things are missing before this page can be published: approved clause copy, an effective date, a contact address, the controller and jurisdiction, a decision on the App Store privacy questionnaire, and a decision on whether the app's privacyPolicy link moves from the placeholder to this address.
  • The facts above are a snapshot, not a warranty. They were read out of the source when this page was written. A policy is drafted from them; it is not these paragraphs with the chips removed.

The Terms of Use is a draft on the same footing — see the Terms of Use page.