Skip to content

UN-3853 [FIX] Attribute platform-key-created resources to the API key's creator - #2274

Open
kirtimanmishrazipstack wants to merge 2 commits into
mainfrom
UN-3853-co-ownership-resources-with-platform-key
Open

UN-3853 [FIX] Attribute platform-key-created resources to the API key's creator#2274
kirtimanmishrazipstack wants to merge 2 commits into
mainfrom
UN-3853-co-ownership-resources-with-platform-key

Conversation

@kirtimanmishrazipstack

@kirtimanmishrazipstack kirtimanmishrazipstack commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What

  • A resource created through a platform API key — a Prompt Studio project, workflow, ETL pipeline, API deployment, connector or adapter — now belongs to the person who created that API key, instead of belonging to nobody.
  • In the rare case where that person has since been deleted from Unstract, the "Owned By" column reads Platform key rather than showing a made-up email address.

Why

  • When a script drives Unstract with a platform API key, Unstract signs in as an invisible robot account belonging to that key, and everything the script created was recorded as owned by the robot.
  • Unstract deliberately hides robots wherever a person's name belongs. So those resources had no human owner at all: they did not appear in anyone's list, and only an org admin could open, share or delete them. The "Owned By" column had nothing real to show — it used to print Unknown, and since UN-3770 [MISC] Make list pagination consistent across shared resource endpoints #2208 prints the robot's synthetic @platform.internal address with an avatar, which reads as a colleague who does not exist.
  • Reported by Chandrasekharan M: a project named "PlatformKey Share Test" showing UN / Unknown in Owned By.
  • Today the damage is limited because only org admins can mint platform keys, and admins can manage everything anyway. But if that admin is demoted or leaves the org, the resource is left with nobody attached to it.

How

  • New helper owner_user_for(user) in backend/platform_api/services.py: if the acting user is a platform key's service account, return the key's created_by; otherwise return the user unchanged. It short-circuits before any query for a normal session, so the UI path is unaffected.
  • This is not a new rule. delete_api_user_for_key in the same module already hands everything a key created over to platform_api_key.created_by when the key is deleted. The change applies that same successor at creation time instead of only at deletion time.
  • The helper is routed into all seven places an OWNER membership row is granted: Prompt Studio create + import, workflow, pipeline, API deployment, connector, adapter.
  • The service account no longer receives a membership row and needs none — every permission class and every for_user() manager already short-circuits on is_service_account.
  • created_by is untouched everywhere. It is an audit field, and "the robot did it" is the truthful answer.
  • renderOwner in ResourceTable.jsx recognises the reserved platform.internal domain and renders Platform key instead of dressing a machine identity up as a person. This is what makes a data migration unnecessary — rows created before this change still read correctly.

Can this PR break any existing features. If yes, please list possible items. If no, please explain why.

  • No. For a normal signed-in session owner_user_for() returns the user unchanged before touching the database, so every UI-driven create behaves exactly as before — bit for bit.
  • The only behaviour that changes is the platform-key path, which today produces an ownerless resource. The service account keeps full access to everything in its org: IsOwner, IsOwnerOrSharedUser, IsParentWorkflowOwner and friends all return True for a service account before ownership is consulted, and for_user() returns the unfiltered queryset for one.
  • delete_api_user_for_key still works. Its membership transfer simply finds nothing left to move, and its created_by/modified_by re-pointing is unchanged and still required.
  • Existing co-owner tests build membership rows directly rather than going through the create endpoints, so none of them exercise the changed line.

Database Migrations

  • None. Resources created before this change keep pointing at a robot, but they read correctly in the UI thanks to the Platform key label, and they are exactly as reachable as they are today (org admins). If we later decide to repair them, the shipped _transfer_membership_rows(key.api_user, key.created_by) does it from a shell — no new code, and no irreversible migration running against production for a handful of rows.

Env Config

  • None.

Relevant Docs

  • None.

Related Issues or PRs

Dependencies Versions

  • None.

Notes on Testing

# Acceptance criterion How to verify Verdict
1 A resource created via a platform API key never renders Unknown in Owned By Create a Prompt Studio project with POST /api/v1/unstract/{org}/prompt-studio/ using a Bearer platform key, then open the Prompt Studio list Met — the column shows the key creator's real email
2 It is reachable by a human owner Sign in as the key's creator (non-admin is the interesting case) and confirm the project appears in the list and can be shared and deleted Met — the creator holds a real OWNER row
3 The fix covers every resource type, not just Prompt Studio Repeat via the workflow, pipeline, API deployment, connector and adapter create endpoints with the same key Met — all seven grant sites route through the helper
4 A genuinely ownerless resource is labelled, not faked Null the key's created_by, then reload the list Met — the cell reads Platform key
5 The UI path is unchanged Create each resource type normally through the UI Met — helper returns early for non-service-account users
6 Existing platform-key resources still behave Load the list as an org admin with a pre-change row present Met — reads Platform key, same access as before

No automated tests added, per the repo convention of not adding tests unless asked. Happy to add coverage for owner_user_for if reviewers want it.

Screenshots

Checklist

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JHnDZZWGhsevUdwgMyR2ai

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR attributes resources created with platform API keys to the human who created the key while preserving the service account in audit fields.

  • Adds centralized platform-key owner resolution and applies it across seven resource-creation paths.
  • Exposes membership-derived owner emails for pipeline and API deployment lists.
  • Centralizes frontend owner rendering and labels residual service-account ownership as “Platform key.”

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains within the eligible follow-up scope.

No blocking failure remains.

Important Files Changed

Filename Overview
backend/platform_api/services.py Adds centralized resolution from a platform-key service account to its human creator, with an explicit service-account fallback when the creator has been deleted.
backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Applies resolved ownership when imported Prompt Studio resources receive their initial OWNER membership.
backend/api_v2/api_deployment_views.py Assigns API deployment ownership to the platform key creator while retaining the acting service account for audit behavior.
backend/pipeline_v2/views.py Assigns pipeline OWNER membership through the new platform-key owner resolver.
frontend/src/components/widgets/owner-display.js Centralizes actual-owner selection, “Me” labeling, co-owner counts, and the residual “Platform key” fallback.
frontend/src/components/widgets/resource-table/ResourceTable.jsx Reuses the centralized owner-display logic for resource table rows.

Sequence Diagram

sequenceDiagram
    participant Client as Platform API client
    participant API as Resource create endpoint
    participant Resolver as owner_user_for
    participant Key as PlatformApiKey
    participant Resource as Created resource
    Client->>API: Create using platform API key
    API->>Resolver: Resolve request.user
    Resolver->>Key: Find key by service account
    alt Creator still exists
        Key-->>Resolver: created_by user
        Resolver-->>API: Human key creator
        API->>Resource: Add OWNER membership for creator
    else Creator was deleted
        Key-->>Resolver: created_by is null
        Resolver-->>API: Service account
        API->>Resource: Add service-account membership
        Note over Resource: UI displays "Platform key"
    end
Loading

Reviews (3): Last reviewed commit: "UN-3853 [FIX] Name the real owner on the..." | Re-trigger Greptile

@kirtimanmishrazipstack

Copy link
Copy Markdown
Contributor Author

Merge this before the cloud PR

Zipstack/unstract-cloud#1753 imports owner_user_for from platform_api.services, and cloud CI pins the OSS tree to refs/heads/main with no override — so its test (integration) and e2e stay red until this merges. Once this lands, a re-run there goes green on its own.

…reator

A platform API key authenticates as a service account, and every resource
create path granted the OWNER membership row to that machine identity. Service
accounts are filtered out of every owner surface (HasMembersMixin), so such a
resource ended up with no human owner: invisible to its creator in list views,
manageable only through the org-admin fallback, and rendered in "Owned By" as a
synthetic @platform.internal address dressed up as a colleague.

Record the key's creator as owner instead — the same successor
delete_api_user_for_key already hands ownership to when a key is deleted, now
applied at creation rather than only at deletion. The service account loses
nothing: permission classes and for_user() short-circuit on is_service_account.

Where no human can be named (the key's creator has since been deleted), the
resource stays deliberately ownerless and the table labels it "Platform key"
rather than naming a machine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JHnDZZWGhsevUdwgMyR2ai
…s too

The ticket asks for Owned By to be correct on every resource type. The API
deployment and ETL pipeline card views were still wrong: OwnerFieldRow read
created_by_email only, and their serializers never exposed owner_emails — so
those cards named the audit creator, which on a platform-key create is the
service account. The backend fix alone could not reach them.

Expose owner_emails on both serializers (their querysets already prefetch
memberships__user, so it costs no extra query), and move the owner-label rule
into one resolveOwnerDisplay helper shared by the table and the cards. The two
had already drifted on both the source field and the "Me" rule — the card said
"Me" to any owner, which is the co-owner bug the table's comment warns about.
Cards now match the table: "Me" tracks the displayed owner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JHnDZZWGhsevUdwgMyR2ai
@kirtimanmishrazipstack
kirtimanmishrazipstack force-pushed the UN-3853-co-ownership-resources-with-platform-key branch from 90013a8 to b818d85 Compare September 3, 2026 06:10
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Frontend Lint Report (Biome)

All checks passed! No linting or formatting issues found.

@sonarqubecloud

sonarqubecloud Bot commented Sep 3, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
e2e-api-deployment e2e 3 0 0 0 20.5
e2e-coowners e2e 1 0 0 0 1.6
e2e-etl e2e 1 0 0 0 8.1
e2e-login e2e 2 0 0 0 1.1
e2e-prompt-studio e2e 1 0 0 0 4.3
e2e-smoke e2e 2 0 0 0 0.9
e2e-workflow e2e 1 0 0 0 18.2
frontend unit 0 1 0 0 0.0
integration-backend integration 310 0 0 26 32.0
integration-connectors integration 1 0 0 7 8.6
integration-workers integration 157 0 0 1 38.4
ui e2e 0 1 0 0 0.0
unit-backend unit 1124 0 0 1 42.7
unit-connectors unit 63 0 0 0 10.2
unit-core unit 33 0 0 0 1.4
unit-platform-service unit 15 0 0 0 2.7
unit-rig unit 120 0 0 0 4.6
unit-runner unit 5 0 0 0 2.9
unit-sdk1 unit 563 0 0 0 29.1
unit-workers unit 1365 0 0 1 124.6
TOTAL 3767 2 0 36 351.9

Critical paths

⚠️ Critical paths not yet covered

  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (declared coverage: no groups declared)
✅ Covered critical paths
  • auth-login — covered by e2e-login
  • adapter-register-llm — covered by integration-backend
  • workflow-author — covered by integration-backend
  • co-owner-manage — covered by integration-backend, e2e-coowners
  • workflow-create-execute — covered by e2e-workflow
  • api-deployment-provision — covered by integration-backend
  • api-deployment-auth — covered by integration-backend
  • api-deployment-run — covered by e2e-api-deployment
  • mcp-server-auth — covered by integration-backend
  • mcp-platform-auth — covered by integration-backend
  • prompt-studio-author — covered by integration-backend
  • prompt-studio-fetch-response — covered by e2e-prompt-studio
  • connector-register-test — covered by integration-backend
  • pipeline-etl-execute — covered by e2e-etl
  • usage-aggregate-read — covered by integration-backend
  • usage-token-tracking — covered by e2e-api-deployment
  • callback-result-delivery — covered by e2e-api-deployment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant