pdt 0.3.5

Asset store API with relation graphs, tagging, full-text search, multi-instance SQLite tenancy, OIDC auth and Cedar authorization
# PDT — Platform Data Toolkit

A document/asset store API with relationship graphs, free-form tagging, full-text search,
OIDC authentication, and Cedar-based authorization. Runs as a single global instance or as
a tree of isolated per-tenant instances, each backed by its own SQLite database.

## Features

- **Assets, relations, collections** — CRUD with soft deletes, a traversable relation
  graph, and named collections
- **Free-form tags**`{category, value}` pairs with light validation; no fixed taxonomy
- **Full-text search** — SQLite FTS5 with user input safely reduced to quoted phrases
- **Multi-instance tenancy**`X-Instance-Id` routing to per-instance SQLite files,
  nested provisioning (workspace → agent/company leaves), instance tree listing
- **Two storage backends** — MongoDB/DocumentDB (default) or SQLite for the root database
- **OIDC auth + Cedar authorization** — JWT validation via a policy enforcement point
  ([pep]https://crates.io/crates/pep), Cedar policies enforced per request, audit log
  with user attribution
- **OpenAPI** — machine-readable spec at `/api/docs/openapi.json`

## Quick start

Zero external dependencies with the SQLite root backend:

```bash
cargo run --features sqlite-backend --bin pdt
# …or let it read a .env file (dotenvy is loaded automatically):
cp env.example .env
```

Environment for the above:

```bash
PDT_DB_BACKEND=sqlite
SQLITE_PATH=./pdt.db
PDT_INSTANCES_DIR=./instances
AUTH_ENABLED=false
AUTH_DEV_MODE=true
```

To run against MongoDB/DocumentDB instead (the default build):

```bash
DOCUMENTDB_URL=mongodb://localhost:27017
DOCUMENTDB_USERNAME=admin
DOCUMENTDB_PASSWORD=change-me
DOCUMENTDB_DATABASE=pdt
```

See `env.example` for the full set of variables.

## Multi-instance tenancy

Every request may carry `X-Instance-Id`. With the header, all entity operations are routed
to that instance's own SQLite database under `PDT_INSTANCES_DIR`; without it, they hit the
root database (MongoDB/DocumentDB by default, SQLite with the `sqlite-backend` feature).

```
POST /api/instances/{id}/provision?parent={parent_id}   # create an instance DB (optionally nested)
GET  /api/instances                                     # list instances found on disk
GET  /api/instances/tree                                # list as a workspace tree
DELETE /api/instances/{id}                              # delete an instance
```

Nested provisioning creates `{instances_dir}/{parent}/{id}/pdt.db`, which gives you
workspace → agent/company hierarchies where each leaf is fully isolated: cross-instance
reads simply don't resolve. Assets are born with an `auth_context`
(visibility / owner groups / confidentiality) that Cedar policies evaluate.

## Authentication and authorization

Write endpoints (and sensitive reads) require `Authorization: Bearer <jwt>`. Tokens are
validated against the configured OIDC issuer (JWKS signature, expiry, audience):

```bash
AUTH_ENABLED=true
AUTH_ISSUER_URL=https://idp.example.com
AUTH_AUDIENCE=pdt-api
AUTH_USERINFO_URL=https://idp.example.com/userinfo   # optional claim enrichment
```

Authorization is delegated to [Cedar](https://www.cedarpolicy.com/). Policies live in
`policies/` (`rbac.cedar` + `schema.cedarschema`), are compiled into the binary, and the
effective policy set is exposed at `GET /api/cedar/policies`.

> ⚠️ **`AUTH_DEV_MODE=true` injects admin claims into any request that carries no bearer
> token — including when `AUTH_ENABLED=true`.** Never enable it outside local development.

## API surface

```
# Assets
POST   /api/assets                          GET    /api/assets
GET    /api/assets/{id}                     PUT    /api/assets/{id}
DELETE /api/assets/{id}                     # soft delete
POST   /api/assets/{id}/tags                DELETE /api/assets/{id}/tags/{tag_id}
PUT    /api/assets/{id}/auth-context        # update auth context
GET    /api/assets/{id}/relations           GET    /api/assets/{id}/graph
GET    /api/assets/{id}/history

# Relations
POST   /api/relations                       GET/DELETE /api/relations/{id}

# Collections
POST   /api/collections                     GET    /api/collections
GET/PUT/DELETE /api/collections/{id}
POST   /api/collections/{id}/assets         DELETE /api/collections/{id}/assets/{asset_id}

# Search & audit
GET    /api/search?q=…                      GET    /api/audit

# Instances
POST   /api/instances/{id}/provision        DELETE /api/instances/{id}
GET    /api/instances                       GET    /api/instances/tree

# Misc
GET    /health                              GET    /api/cedar/policies
```

### Search

`GET /api/search?q=fame workspace` — queries are tokenized and each token is phrase-quoted
before hitting FTS5, so user input can never be interpreted as FTS query syntax
(column filters, boolean operators, etc.). Non-alphanumeric characters are preserved
inside quoted phrases, making e.g. Persian text searchable.

### Tags

Tags are free-form `{category, value}` pairs — categories like `asset_type` are
conventions, not enforced values. Validation: category/value are alphanumeric plus
hyphens, underscores, and forward slashes, capped at 64 characters.

### Relation types

`contains`, `references`, `related_to`, `depends_on`, `supersedes`, `complements`,
`has_instance`, `has_agent`

## Development

```bash
cargo test                            # default (mongodb-backend) suite
cargo test --features sqlite-backend  # sqlite root-backend suite
```

Two binaries are produced by the workspace; `--bin pdt` selects the server. The
`sqlite-backend` feature gates only the **root** database repository — instance routing
always uses SQLite, so `sqlx` is compiled in unconditionally.

## License

Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.