Share and Revoke Without Leaking Authority
Let a customer share one generated application without confusing platform membership, runtime identity, and application access.
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 retire(self, project: Project, actor: str) -> None:
self._owner(project, actor)
project.status = "retired"
with self.repository.transaction():
self.repository.save(project)
self._audit(project, actor, "project.retired")
self.runtime.delete(project.organization_id, project.project_id)
def delete(self, project: Project, actor: str) -> None:
self._owner(project, actor)
if project.status != "retired":
raise ValueError("retire project before deletion")
Code to reality
- Declared intent
- Enforce the product lifecycle so published runtime state is retired before durable deletion.
- Interpreter
- The injected Forge model applies ownership and state-machine rules independently of HTTP or storage technology.
- Software effect
- Retirement removes the hosted runtime and records status before deletion can tombstone control-plane state.
- Hardware effect
- Runtime containers and storage can be released in order while retained evidence remains physically durable.
- Observable evidence
- A denied early delete, successful retirement, absent runtime, tombstoned project, and retained audit event prove order.
Start with the people and the result they need
The source tables below remain the detailed contract. Begin with these customer paths:
- D11-UC-01
- Person: Workspace owner
- Job: Share one Research Brief, Service Desk, or Field Inspection application with a named collaborator
- Observable result: Collaborator opens only the granted application and sees only authorised generated-application data
- D11-UC-02
- Person: Organization administrator
- Job: Revoke a collaborator without disabling the application for its owner
- Observable result: Existing collaborator sessions stop authorising new requests and the owner path remains healthy
Turn each customer job into a testable story
Now turn each customer job into a story with a result that an engineer can check:
- D11-US-01
- Story: As a workspace owner, I want to grant a collaborator a named application role, so that they can use one application without inheriting my workspace…
- Observable acceptance: Grant names application, tenant, role, expiry, grantor, grantee, environment, and trace; positive and outsider probes are recorded
- D11-US-02
- Story: As an organization administrator, I want to revoke one grant independently, so that access ends without an application outage
- Observable acceptance: New requests using the revoked grant are denied, cached sessions expire or are invalidated, and an owner positive control still passes
Add real state and observable proof
Finally trace each story through the system that owns its state and the evidence that proves the outcome:
- D11-FLOW-01
- Trigger: Owner selects Share, collaborator, role, and expiry
- Responsible systems: Control-plane authorization, Grant Service, Runtime Session Gateway, generated-app Policy Evaluator
- Authoritative state: Grant Service database for cross-plane mapping; generated-app database for roles and tenant predicates
- Owned record: ApplicationGrant
- Observable evidence: Allowed and denied results with actor, application, tenant, scope, precondition, expected and observed result, environment, timestamp, grant ID, and…
- Failure signal: Unknown grantee, cross-tenant role request, expired grant, or outsider denial trace
- D11-FLOW-02
- Trigger: Administrator selects Revoke access
- Responsible systems: Grant Service, invalidation stream, Runtime Session Gateway, generated-app Policy Evaluator
- Authoritative state: Grant Service database for grant status and revocation time
- Owned record: AppRoleBinding
- Observable evidence: Revocation record, denied former collaborator trace, owner response trace, environment, timestamp, and immutable run ID
- Failure signal: Stale session accepted after cutoff, missing invalidation acknowledgement, or owner control failure
The enterprise problem and today’s slice
Enterprise problem: A workspace owner needs to share a Zheta Forge application, but copying a URL or reusing workspace membership can expose another tenant’s data and leave no reliable way to revoke access. Whole-course context: The current platform has provider identity, project state, artifact preview, and connector metadata; today adds a named provider collaborator. Today’s slice: We implement and test provider-plane share/revoke while preserving separate hosted-runtime and generated-app grant designs that do not yet exist in code. End-of-day evidence: Durable collaborator state, owner-only mutation, revoked-collaborator denial, unaffected owner access, and share/revoke audit actions prove the implemented boundary. Still unsolved: App-local roles, expiry, session invalidation, immutable publication, public release policy, and rollback remain deferred.
Customer use cases
Sharing fails dangerously when “can open the URL” is mistaken for “may read this tenant’s records,” so each grant needs an explicit actor, resource, scope, and expiry. These use cases cover both access and its independent removal.
| Use case ID | Actor | Customer job | Success outcome | Denial or recovery evidence |
|---|---|---|---|---|
| D11-UC-01 | Workspace owner | Share one Research Brief, Service Desk, or Field Inspection application with a named collaborator | Collaborator opens only the granted application and sees only authorised generated-application data | An ungranted user receives a recorded denial while the owner remains able to open the application |
| D11-UC-02 | Organization administrator | Revoke a collaborator without disabling the application for its owner | Existing collaborator sessions stop authorising new requests and the owner path remains healthy | Revocation trace identifies the grant and session cutoff; repeated revocation is harmless and auditable |
Actor-centred user stories
A share button is incomplete if nobody can state what it authorises or how denial is observed. These stories bind the customer action to positive and negative outcomes.
| Story ID | Use case IDs | User story | Observable acceptance conditions |
|---|---|---|---|
| D11-US-01 | D11-UC-01 | As a workspace owner, I want to grant a collaborator a named application role, so that they can use one application without inheriting my workspace authority | Grant names application, tenant, role, expiry, grantor, grantee, environment, and trace; positive and outsider probes are recorded |
| D11-US-02 | D11-UC-02 | As an organization administrator, I want to revoke one grant independently, so that access ends without an application outage | New requests using the revoked grant are denied, cached sessions expire or are invalidated, and an owner positive control still passes |
End-to-end product flows
Access decisions become untrustworthy when the product cannot reconstruct how a click became a generated-application permission. The flows begin at customer-visible share and revoke actions and end in observed evidence.
| Flow ID | Use case IDs | Path | Trigger | Numbered steps | Terminal evidence |
|---|---|---|---|---|---|
| D11-FLOW-01 | D11-UC-01 | Happy | Owner selects Share, collaborator, role, and expiry | 1. Control plane verifies grantor authority. 2. Grant service creates an application-scoped grant. 3. Runtime exchanges it for a short session. 4. Generated application evaluates tenant and role predicates. 5. Test the collaborator and outsider paths. | Allowed and denied results with actor, application, tenant, scope, precondition, expected and observed result, environment, timestamp, grant ID, and trace IDs |
| D11-FLOW-02 | D11-UC-02 | Recovery | Administrator selects Revoke access | 1. Mark the exact grant revoked. 2. Publish a session-invalidation event. 3. Reject later requests carrying that grant. 4. Probe the owner as an unaffected positive control. | Revocation record, denied former collaborator trace, owner response trace, environment, timestamp, and immutable run ID |
The owner acts on one application, and the named collaborator is the intended recipient. No organization, workspace, or application role is silently copied by this path.
System design derived from the flows
One identity token cannot safely cross all three planes because each plane owns different resources and revocation rules. An explicit grant service maps opaque identifiers while each boundary still performs its own authorization.
| Use case ID | Entry point | Responsible services | Authoritative store | Failure evidence |
|---|---|---|---|---|
| D11-UC-01 | Control-plane Share action | Control-plane authorization, Grant Service, Runtime Session Gateway, generated-app Policy Evaluator | Grant Service database for cross-plane mapping; generated-app database for roles and tenant predicates | Unknown grantee, cross-tenant role request, expired grant, or outsider denial trace |
| D11-UC-02 | Control-plane Revoke action | Grant Service, invalidation stream, Runtime Session Gateway, generated-app Policy Evaluator | Grant Service database for grant status and revocation time | Stale session accepted after cutoff, missing invalidation acknowledgement, or owner control failure |
The Grant Service owns the provider mapping, the Session Gateway owns runtime sessions, and the Policy Evaluator owns the final generated-application decision. Their identifiers are opaque references, not inherited authority, and each mapping is least-privilege, audited, and independently revocable.
Data model and ownership
Revocation cannot be proven from an audit message alone because the durable grant and generated-app role must retain separate owners. The model records their relationship without moving domain data into the control plane.
Generated-application database: Not created in this runnable slice — the current model owns provider project collaborators only; generated-app tenant roles, visibility predicates, sessions, and domain authorization remain a separate target boundary.
| Record or entity | Store and owner | Primary key | Foreign key or opaque reference | Tenant key | Material constraint | Lifecycle and deletion | Use case IDs |
|---|---|---|---|---|---|---|---|
| ApplicationGrant | Control-plane Grant Store, owned by Grant Service | grant_id | Opaque application_release_id and generated_app_subject_ref | organization_id | One active grant per grantee, application, role, and scope; revoked grants cannot reactivate | Create, expire or revoke, retain for audit, then tombstone by policy | D11-UC-01, D11-UC-02 |
| AppRoleBinding | Generated-application database, owned by Policy Evaluator | role_binding_id | Opaque grant_id; no control-plane foreign key | app_tenant_id | Subject must belong to tenant and role must be declared by this application | Create from valid mapping, disable on revocation, export with tenant, delete with application retention policy | D11-UC-01, D11-UC-02 |
| RuntimeSession | Runtime session store, owned by Session Gateway | session_id | Opaque grant_id and role_binding_id | runtime_tenant_id | Expiry and revocation cutoff are checked on every privileged request | Issue, rotate, invalidate, expire quickly, delete after security retention | D11-UC-01, D11-UC-02 |
The durable grant moves from active to expired or revoked, after which runtime and application checks deny it. The paired positive and negative traces prove revocation without mistaking a broad outage for correct authorization.
Run the grant evidence loop
A configuration that looks scoped can still be implemented as a permissive check, so exercise both sides of the boundary. The current Python model implements owner-controlled provider collaboration, including the rule that the owner cannot be revoked; it has no generated-app role, expiry, or session service. Read the exact methods in services/control_plane/domain.py.
def share(self, project: Project, actor: str, collaborator: str) -> None:
self._owner(project, actor)
project.collaborators.add(collaborator)
self._save_and_audit(project, actor, "collaborator.shared")
def revoke(self, project: Project, actor: str, collaborator: str) -> None:
self._owner(project, actor)
if collaborator == project.owner:
raise ValueError("owner cannot be revoked")
project.collaborators.discard(collaborator)
self._save_and_audit(project, actor, "collaborator.revoked")
| Effect field | What happens |
|---|---|
| Declared intent | Only the project owner can add or remove one provider-plane collaborator, and the owner remains non-revocable |
| Interpreter | Python executes Forge.share or Forge.revoke; injected repository and event ports own persistence and evidence |
| Software effect | The collaborator set is saved and collaborator.shared or collaborator.revoked is published; no role, expiry, or app session changes |
| Hardware effect | The control-plane process consumes CPU and local SQLite/broker paths consume storage and networking |
| Evidence | Authenticated HTTP response, restored collaborator set, collaborator denial after revoke, owner positive control, and audit action |
Key takeaways
Sharing is an explicit, revocable mapping among three independently authorised planes, not a copied URL or inherited role.
- Provider membership does not grant runtime or generated-application access.
- Revocation needs a negative probe and an unaffected positive control.
- Opaque references connect owners without transferring ownership.
Checklist
An access change is complete only when its scope and removal are observable.
- [ ] Grant names application, tenant, role, subject, grantor, and expiry
- [ ] Outsider denial and collaborator allowance are recorded
- [ ] Revocation invalidates future requests without disabling the owner
- [ ] Evidence contains actor, resource, scope, environment, time, run, and trace IDs