Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: optional tenant_id field on resource models (cloud prereq, v0.4.0) #68

@ascender1729

Description

@ascender1729

Problem

Resources (UAITs, credentials, delegations, compliance profiles, etc.) have no concept of tenant ownership. The whole engine assumes a single global namespace, which is fine for self-host but means the cloud product cannot store multiple customers' data side-by-side without engine forks.

The cleanest fix is upstream: add an optional tenant_id field that defaults to a sentinel value ("default") for self-host installs. Self-hosters never see it; cloud uses it for RLS scoping and audit chain isolation per workspace.

Proposed solution

Thread tenant_id through the resource models and the signable payload:

# services/identity_service.py
def create_identity(
    self,
    display_name: str,
    source_protocol: str,
    *,  # keyword-only from here
    tenant_id: str = "default",     # NEW
    identity_token: str = "",
    capabilities: Optional[List[str]] = None,
    description: str = "",
    issuer_name: str = "",
    expiry_days: Optional[int] = None,
) -> dict:
    ...
    uait = {
        "version": UAIT_VERSION,
        "agent_id": agent_id,
        "tenant_id": tenant_id,           # NEW field on the resource
        "display_name": display_name,
        ...
    }

Same pattern on:

  • CredentialService.issue_credential -> credential gets tenant_id
  • DelegationService.create_delegation -> delegation gets tenant_id
  • ComplianceService.create_profile -> profile gets tenant_id
  • ProvenanceService.record_* -> provenance entry gets tenant_id
  • ReputationService.record_interaction -> interaction gets tenant_id

Optional read-side filtering:

def list_identities(
    self,
    *,
    tenant_id: Optional[str] = None,   # NEW; None means "no filter"
    source_protocol: Optional[str] = None,
    include_revoked: bool = False,
    limit: int = 50,
) -> List[dict]:
    data = self.storage.load_identities()
    results = []
    for agent in data["agents"]:
        if tenant_id is not None and agent.get("tenant_id", "default") != tenant_id:
            continue
        ...

tenant_id is part of the signable payload (immutable, included in _signable_payload). Existing self-host data without the field reads as "default" for verification purposes - this needs careful handling so existing signatures still verify after the upgrade.

Backward compatibility

  • Existing JSON files have no tenant_id field. Loaders treat missing field as "default".
  • Existing signatures over old payloads continue to verify because _signable_payload should treat absent tenant_id as "default" rather than as a missing field. Document this explicitly in auth/crypto.py.
  • API responses gain a tenant_id key but self-host clients can ignore it.

Acceptance criteria

  • All 359 existing tests pass; existing JSON test fixtures load without errors and verify their original signatures.
  • New tests cover: creating with explicit tenant_id, listing with tenant_id filter, signature verification on a payload that explicitly carries tenant_id, signature verification on a legacy payload that lacks it.
  • The CLI does not require --tenant-id; defaults to "default".
  • OpenAPI spec gets the new optional field documented.
  • DCO sign-off.

Dependencies

Soft-blocked by # because tenant_id filtering is more natural once the storage layer is pluggable (cloud's PostgresStorage will push the filter into a SQL WHERE tenant_id = $1 clause via RLS rather than filtering in Python). Can be done before but the integration with cloud is cleaner after.

Cloud context

Cloud's Postgres adapter sets app.tenant_id GUC per request and RLS policies on every table use it. This OSS PR adds the field on the resource so the cryptographic surface stays consistent across self-host and cloud.

Reference: attestix-cloud-plan/07-DATA-MODEL.md (multi-tenant RLS), 02-OPEN-CORE.md "What changes in attestix because of cloud".

Suggested commit message

feat(tenant): optional tenant_id field on resource models

Adds an optional tenant_id field (default: "default") to UAIT,
credential, delegation, compliance profile, provenance entry, and
reputation interaction models. Threaded through service create/list
methods as a keyword-only parameter.

tenant_id is part of the signable payload; legacy data without the
field is treated as tenant_id="default" for both reads and signature
verification, preserving compatibility with existing self-host
deployments.

Refs: #<issue>

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions