07

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 IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D07-UC-01Application creatorAdd durable domain data to a policy portal, approval workflow, or support dashboardApp migration succeeds and a tenant-scoped record survives pod replacementFailed migration leaves prior schema usable and records rollback evidence
D07-UC-02Generated-app tenant administratorKeep one app tenant's records inaccessible to another tenantValid tenant reads its records through app authorization predicatesCross-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 IDUse case IDsUser storyObservable acceptance conditions
D07-US-01D07-UC-01As an application creator, I want a versioned managed-data contract, so that schema changes and recovery are reproducibleProvision ID, database binding, migration digest, backup ID, and post-restart read are recorded
D07-US-02D07-UC-02As a generated-app tenant administrator, I want tenant predicates enforced on every domain query, so that knowing a record ID cannot bypass isolationWrong-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 IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D07-FLOW-01D07-UC-01, D07-UC-02HappyCreator selects Enable managed data1. 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-02D07-UC-01, D07-UC-02DeniedTenant B requests Tenant A's record ID1. 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 IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D07-UC-01Forge Studio managed-data actionData Provisioner, migration runner, secret/workload-identity adapter, generated appProvision record in control plane; generated-app PostgreSQL for domain dataProvision error, migration failure, failed readiness, or missing post-restart record
D07-UC-02Generated-app domain APIRuntime Gateway, generated-app session and repository policy, Audit EvidenceGenerated-app PostgreSQL and app audit trailMissing 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 entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
DataProvisionControl-plane PostgreSQL, owned by Data Provisionerprovision_idOpaque application, database, binding, and backup IDsorganization_idOne active provision per app environment; no credentials stored in rowRequested, ready/failed, suspended, exported, deleted, tombstonedD07-UC-01, D07-UC-02
DomainRecordGenerated-app PostgreSQL, owned by generated applicationrecord_idLocal app-domain foreign keys; opaque source reference if neededapp_tenant_idEvery access predicate includes app_tenant_id; domain uniqueness is tenant-scopedCreated/updated by app; exported and deleted by app lifecycle policyD07-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 intentInterpreterSoftware effectHardware effectEvidence
Restrict control-plane project retrieval by organization, project identity, and tombstone statePython executes JsonProjectRepository; SQLite interprets the local SQL and a configured PostgreSQL adapter can translate placeholdersReturns only the matching live provider record; it neither creates nor authorizes generated-app domain rowsThe database process uses CPU, memory, and storage pages for the indexed lookuptest_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.