Give Generated Apps Managed Data Without Losing Ownership
Provision durable application data through a platform contract while keeping each generated app responsible for its own tenants and authorization predicates.
Run it in the public monorepo
This course is built around the public Zheta Kubernetes Lab monorepo. The excerpt below is runnable source, not pseudocode.
Source: services/control_plane/domain.py
def create(self, project_id: str, organization_id: str, name: str, actor: str, archetype: str) -> Project:
project = Project(project_id, organization_id, name, actor, blueprint={"name": name, "archetype": archetype})
project.collaborators.add(actor)
with self.repository.transaction():
self.repository.create(project)
self._audit(project, actor, "project.created")
return project
Code to reality
- Declared intent
- Create one tenant-owned project and its audit evidence as a single product operation.
- Interpreter
- Python executes the Forge domain model after the controller supplies authenticated command values.
- Software effect
- The repository durably owns the project while the event port publishes project.created evidence.
- Hardware effect
- The service process uses CPU and writes database pages to its persistent volume or managed database.
- Observable evidence
- A tenant-scoped GET plus a project.created evidence record proves both state and accountability.
Start with the people and the result they need
The source tables below remain the detailed contract. Begin with these customer paths:
- D07-UC-01
- Person: Application creator
- Job: Add durable domain data to a policy portal, approval workflow, or support dashboard
- Observable result: App migration succeeds and a tenant-scoped record survives pod replacement
- D07-UC-02
- Person: Generated-app tenant administrator
- Job: Keep one app tenant's records inaccessible to another tenant
- Observable result: Valid tenant reads its records through app authorization predicates
Turn each customer job into a testable story
Now turn each customer job into a story with a result that an engineer can check:
- D07-US-01
- Story: As an application creator, I want a versioned managed-data contract, so that schema changes and recovery are reproducible
- Observable acceptance: Provision ID, database binding, migration digest, backup ID, and post-restart read are recorded
- D07-US-02
- Story: As a generated-app tenant administrator, I want tenant predicates enforced on every domain query, so that knowing a record ID cannot bypass isolation
- Observable acceptance: Wrong-tenant request is denied and audited; right-tenant positive control remains successful
Add real state and observable proof
Finally trace each story through the system that owns its state and the evidence that proves the outcome:
- D07-FLOW-01
- Trigger: Creator selects Enable managed data
- Responsible systems: Data Provisioner, migration runner, secret/workload-identity adapter, generated app
- Authoritative state: Provision record in control plane; generated-app PostgreSQL for domain data
- Owned record: DataProvision
- Observable evidence: Actor, app, provision ID, binding, migration digest, record ID, replacement Pod UID, read trace, timestamp
- Failure signal: Provision error, migration failure, failed readiness, or missing post-restart record
- D07-FLOW-02
- Trigger: Tenant B requests Tenant A's record ID
- Responsible systems: Runtime Gateway, generated-app session and repository policy, Audit Evidence
- Authoritative state: Generated-app PostgreSQL and app audit trail
- Owned record: DomainRecord
- Observable evidence: Actor, app tenant, record, predicate policy, denied result, positive-control trace, environment, immutable run ID
- Failure signal: Missing tenant predicate, cross-tenant row, denial without trace, or failed positive control
The enterprise problem and today’s slice
Enterprise problem: A shared database with provider-wide credentials lets one generated app read another app's records and makes export or deletion inseparable from platform metadata.
Whole-course context: The local preview runtime durably stores deployment payloads but is not an isolation sandbox; today separates that hosted-runtime state from the future generated-app domain database.
Today’s slice: Design the generated-application PostgreSQL ownership contract, then identify the gap between that production target and the current local runtime, which persists deployment payloads but has no generated-app domain database.
End-of-day evidence: The existing domain test proves organization-scoped provider lookup, restart durability, tombstoning, and retained evidence; a gap record names the missing app-tenant query, migration, backup, export, and deletion proofs.
Still unsolved: Enterprise-source connectors, delegated identity, sharing, publication, and multi-account AWS data services remain deferred.
Customer use cases
Managed data is useful only when application code owns domain rules and the platform owns lifecycle automation. These use cases prove both durable writes and denied cross-tenant reads.
| Use case ID | Actor | Customer job | Success outcome | Denial or recovery evidence |
|---|---|---|---|---|
| D07-UC-01 | Application creator | Add durable domain data to a policy portal, approval workflow, or support dashboard | App migration succeeds and a tenant-scoped record survives pod replacement | Failed migration leaves prior schema usable and records rollback evidence |
| D07-UC-02 | Generated-app tenant administrator | Keep one app tenant's records inaccessible to another tenant | Valid tenant reads its records through app authorization predicates | Cross-tenant query is denied while the original tenant still reads its record |
Actor-centred user stories
Database connectivity is not authorization. These stories require application-level tenant policy plus platform-level credential isolation.
| Story ID | Use case IDs | User story | Observable acceptance conditions |
|---|---|---|---|
| D07-US-01 | D07-UC-01 | As an application creator, I want a versioned managed-data contract, so that schema changes and recovery are reproducible | Provision ID, database binding, migration digest, backup ID, and post-restart read are recorded |
| D07-US-02 | D07-UC-02 | As a generated-app tenant administrator, I want tenant predicates enforced on every domain query, so that knowing a record ID cannot bypass isolation | Wrong-tenant request is denied and audited; right-tenant positive control remains successful |
End-to-end product flows
The customer enables managed data from the application settings, not by receiving a master password. A generated workload obtains only its app-scoped binding.
| Flow ID | Use case IDs | Path | Trigger | Numbered steps | Terminal evidence |
|---|---|---|---|---|---|
| D07-FLOW-01 | D07-UC-01, D07-UC-02 | Happy | Creator selects Enable managed data | 1. Authorize app revision.; 2. Data Provisioner creates app database and workload binding.; 3. Migration job applies digest.; 4. App writes tenant record.; 5. Pod is replaced.; 6. App reads the record. | Actor, app, provision ID, binding, migration digest, record ID, replacement Pod UID, read trace, timestamp |
| D07-FLOW-02 | D07-UC-01, D07-UC-02 | Denied | Tenant B requests Tenant A's record ID | 1. App session resolves Tenant B.; 2. Repository adds tenant predicate.; 3. Query returns no authorized record.; 4. Audit stores denial.; 5. Tenant A repeats read. | Actor, app tenant, record, predicate policy, denied result, positive-control trace, environment, immutable run ID |
The creator requests a product capability. The platform provisions infrastructure but never becomes the owner of app-domain authorization.
System design derived from the flows
Giving every service a database password would erase least privilege. Data Provisioner creates a binding; the generated app alone implements its domain schema and tenant predicate.
| Use case ID | Entry point | Responsible services | Authoritative store | Failure evidence |
|---|---|---|---|---|
| D07-UC-01 | Forge Studio managed-data action | Data Provisioner, migration runner, secret/workload-identity adapter, generated app | Provision record in control plane; generated-app PostgreSQL for domain data | Provision error, migration failure, failed readiness, or missing post-restart record |
| D07-UC-02 | Generated-app domain API | Runtime Gateway, generated-app session and repository policy, Audit Evidence | Generated-app PostgreSQL and app audit trail | Missing tenant predicate, cross-tenant row, denial without trace, or failed positive control |
Data Provisioner owns lifecycle metadata and binding delivery. The generated application owns rows, roles, and authorization inside its database.
Data model and ownership
Provider metadata and customer domain rows need separate deletion and export rules. An opaque provision reference connects them without transferring authority.
Generated-application database: Not created in this runnable slice — the monorepo persists provider project/artifact state and hosted-runtime payloads, while the generated-app PostgreSQL contract below remains a production design requiring its own adapter, migration, tenant-denial, backup, and deletion proof.
| Record or entity | Store and owner | Primary key | Foreign key or opaque reference | Tenant key | Material constraint | Lifecycle and deletion | Use case IDs |
|---|---|---|---|---|---|---|---|
| DataProvision | Control-plane PostgreSQL, owned by Data Provisioner | provision_id | Opaque application, database, binding, and backup IDs | organization_id | One active provision per app environment; no credentials stored in row | Requested, ready/failed, suspended, exported, deleted, tombstoned | D07-UC-01, D07-UC-02 |
| DomainRecord | Generated-app PostgreSQL, owned by generated application | record_id | Local app-domain foreign keys; opaque source reference if needed | app_tenant_id | Every access predicate includes app_tenant_id; domain uniqueness is tenant-scoped | Created/updated by app; exported and deleted by app lifecycle policy | D07-UC-01, D07-UC-02 |
The lifecycle record proves what the platform manages; application evidence proves the app's own data survived and remained tenant-isolated.
Prove persistence and tenant denial
A successful database connection says nothing about authorization. The current code proves only that repository lookups include both organization_id and project_id; it does not implement the generated-application database, schema migration, or app-tenant predicate described above. Read the actual local persistence seam in services/shared/persistence.py.
def get(self, organization_id: str, project_id: str) -> dict[str, object] | None:
row = self.database.one("SELECT payload FROM projects WHERE organization_id=? AND project_id=? AND deleted_at IS NULL", (organization_id, project_id))
return json.loads(row[0]) if row else None
| Declared intent | Interpreter | Software effect | Hardware effect | Evidence |
|---|---|---|---|---|
| Restrict control-plane project retrieval by organization, project identity, and tombstone state | Python executes JsonProjectRepository; SQLite interprets the local SQL and a configured PostgreSQL adapter can translate placeholders | Returns only the matching live provider record; it neither creates nor authorizes generated-app domain rows | The database process uses CPU, memory, and storage pages for the indexed lookup | test_tenant_boundary_and_tombstone_preserve_evidence proves wrong-organization absence and retained evidence; generated-app tenant denial remains unproved |
Decision rules
Use platform automation to provision, bind, back up, export, and delete data services; keep schema semantics and row authorization in the generated application. Never use an opaque cross-plane reference as proof of permission.