grantr 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. grantr-0.1.0/LICENSE +21 -0
  2. grantr-0.1.0/PKG-INFO +111 -0
  3. grantr-0.1.0/README.md +68 -0
  4. grantr-0.1.0/pyproject.toml +142 -0
  5. grantr-0.1.0/pyproject.toml.orig +116 -0
  6. grantr-0.1.0/src/grantr/__init__.py +84 -0
  7. grantr-0.1.0/src/grantr/core/__init__.py +123 -0
  8. grantr-0.1.0/src/grantr/core/audit.py +242 -0
  9. grantr-0.1.0/src/grantr/core/errors.py +13 -0
  10. grantr-0.1.0/src/grantr/core/grants.py +98 -0
  11. grantr-0.1.0/src/grantr/core/ids.py +31 -0
  12. grantr-0.1.0/src/grantr/core/keys.py +59 -0
  13. grantr-0.1.0/src/grantr/core/resources.py +67 -0
  14. grantr-0.1.0/src/grantr/core/roles.py +332 -0
  15. grantr-0.1.0/src/grantr/core/subjects.py +71 -0
  16. grantr-0.1.0/src/grantr/fastapi/__init__.py +30 -0
  17. grantr-0.1.0/src/grantr/fastapi/openapi.py +45 -0
  18. grantr-0.1.0/src/grantr/fastapi/router.py +541 -0
  19. grantr-0.1.0/src/grantr/grants/__init__.py +19 -0
  20. grantr-0.1.0/src/grantr/grants/grants.py +991 -0
  21. grantr-0.1.0/src/grantr/grants/memory.py +299 -0
  22. grantr-0.1.0/src/grantr/grants/storage.py +200 -0
  23. grantr-0.1.0/src/grantr/py.typed +0 -0
  24. grantr-0.1.0/src/grantr/sql/__init__.py +18 -0
  25. grantr-0.1.0/src/grantr/sql/migrations/env.py +17 -0
  26. grantr-0.1.0/src/grantr/sql/migrations/script.py.mako +26 -0
  27. grantr-0.1.0/src/grantr/sql/migrations/versions/0001_initial.py +124 -0
  28. grantr-0.1.0/src/grantr/sql/migrations/versions/0002_resources.py +46 -0
  29. grantr-0.1.0/src/grantr/sql/migrations/versions/0003_row_level_security.py +65 -0
  30. grantr-0.1.0/src/grantr/sql/migrations/versions/0004_audit_filters.py +117 -0
  31. grantr-0.1.0/src/grantr/sql/migrations/versions/0005_tenant_creation_in_the_deployment_log.py +48 -0
  32. grantr-0.1.0/src/grantr/sql/schema.py +113 -0
  33. grantr-0.1.0/src/grantr/sql/sqlite.py +18 -0
  34. grantr-0.1.0/src/grantr/sql/storage.py +728 -0
  35. grantr-0.1.0/src/grantr/sql/tables.py +163 -0
  36. grantr-0.1.0/src/grantr/sql/tenancy.py +18 -0
  37. grantr-0.1.0/src/grantr/telemetry/__init__.py +105 -0
  38. grantr-0.1.0/src/grantr/testing/__init__.py +41 -0
grantr-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Nodeland
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
grantr-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: grantr
3
+ Version: 0.1.0
4
+ Summary: Identity and access for artifactr and reflexr: tenants, members, groups, roles, service principals and grants, checked before every command and read.
5
+ Keywords: authorization,access-control,rbac,multi-tenant,agents,pydantic
6
+ Author: Alex Nodeland
7
+ Author-email: Alex Nodeland <alex@ournature.studio>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Framework :: Pydantic :: 2
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Security
19
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
20
+ Classifier: Typing :: Typed
21
+ Requires-Dist: pydantic>=2.13
22
+ Requires-Dist: opentelemetry-api>=1.28
23
+ Requires-Dist: fastapi>=0.141 ; extra == 'fastapi'
24
+ Requires-Dist: starlette>=0.46.0 ; extra == 'fastapi'
25
+ Requires-Dist: grantr[sql] ; extra == 'postgres'
26
+ Requires-Dist: asyncpg>=0.31 ; extra == 'postgres'
27
+ Requires-Dist: lattice-core-ai[sql]>=0.1.0 ; extra == 'sql'
28
+ Requires-Dist: sqlalchemy[asyncio]>=2.1 ; extra == 'sql'
29
+ Requires-Dist: alembic>=1.20 ; extra == 'sql'
30
+ Requires-Dist: grantr[sql] ; extra == 'sqlite'
31
+ Requires-Dist: aiosqlite>=0.22 ; extra == 'sqlite'
32
+ Requires-Python: >=3.12
33
+ Project-URL: Homepage, https://lattice.alexnodeland.com/grantr/
34
+ Project-URL: Documentation, https://lattice.alexnodeland.com/grantr/
35
+ Project-URL: Repository, https://github.com/alexnodeland/lattice
36
+ Project-URL: Issues, https://github.com/alexnodeland/lattice/issues
37
+ Project-URL: Changelog, https://github.com/alexnodeland/lattice/blob/main/packages/grantr/CHANGELOG.md
38
+ Provides-Extra: fastapi
39
+ Provides-Extra: postgres
40
+ Provides-Extra: sql
41
+ Provides-Extra: sqlite
42
+ Description-Content-Type: text/markdown
43
+
44
+ <picture>
45
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/grantr/assets/brand/banner-dark.svg">
46
+ <img alt="grantr: identity and access for artifactr, reflexr and portalr." src="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/grantr/assets/brand/banner-light.svg" width="100%">
47
+ </picture>
48
+
49
+ <p>
50
+ <a href="https://lattice.alexnodeland.com/grantr/"><img alt="Docs" src="https://img.shields.io/badge/docs-lattice.alexnodeland.com%2Fgrantr-6B5300"></a>
51
+ <a href="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml"><img alt="Nightly" src="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml/badge.svg?branch=main"></a>
52
+ <img alt="Python 3.12, 3.13 and 3.14" src="https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-6B5300">
53
+ <img alt="Coverage: 100%" src="https://img.shields.io/badge/coverage-100%25-6B5300">
54
+ <img alt="Typed: pyright strict" src="https://img.shields.io/badge/typed-pyright%20strict-6B5300">
55
+ <a href="https://github.com/alexnodeland/lattice/blob/main/packages/grantr/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-6B5300"></a>
56
+ </p>
57
+
58
+ **grantr** is the identity and access package for [artifactr](https://lattice.alexnodeland.com/artifactr/), [reflexr](https://lattice.alexnodeland.com/reflexr/) and [portalr](https://lattice.alexnodeland.com/portalr/): tenants, their members, groups, roles and service principals, and the grants that bind them to tenants, workspaces, threads, artifacts and rules. The libraries ask it, through their own hooks, before every command and read. It imports neither library, and neither imports it.
59
+
60
+ > **Status:** v0.1's scope is built: the model, built-in and custom roles, groups, service principals and keys, checks, the libraries' hooks, restricted threads and artifacts, the audit log, in-memory and SQL storage with row-level security (the `sql`, `sqlite` and `postgres` extras), the admin router (the `fastapi` extra) and telemetry. stackr's template wires it in by default, so each library asks it before every command and read.
61
+
62
+ ## Why
63
+
64
+ Before grantr, any user of a tenant could do anything in every workspace of that tenant. The libraries' `authorize` hook was asked when a workspace was opened, and never heard what the actor was about to do, so a viewer couldn't be kept from editing. A user had exactly one tenant, set in their token, and nothing stored members, groups or roles. [lattice RFC-0001](https://lattice.alexnodeland.com/rfcs/0001-grantr-identity-and-access/) designs access once, for every surface, and grantr is where its model is built.
65
+
66
+ ## What it does
67
+
68
+ - **Check every command and read.** The libraries' hook hears the action, such as `artifactr:edit_artifact`, and asks grantr before each command and read, on every surface ([ADR-0001](https://lattice.alexnodeland.com/grantr/adr/0001-the-hook-hears-the-action/)).
69
+ - **Grant down to threads and artifacts,** and hide a restricted one from everyone without a role on it ([ADR-0002](https://lattice.alexnodeland.com/grantr/adr/0002-grants-down-to-threads-and-artifacts/), [ADR-0003](https://lattice.alexnodeland.com/grantr/adr/0003-visible-to-the-workspace-unless-restricted/)).
70
+ - **Act for a user.** An agent's turn is checked as the person whose message started it ([ADR-0004](https://lattice.alexnodeland.com/grantr/adr/0004-an-agent-turn-acts-for-its-user/)).
71
+ - **Own membership.** A user's tenants come from grantr, and each request names its tenant ([ADR-0005](https://lattice.alexnodeland.com/grantr/adr/0005-a-users-tenants-from-grantrs-memberships/)).
72
+ - **Built-in and custom roles, groups and service principals,** with keys shown once, and checks that deny by default ([ADR-0006](https://lattice.alexnodeland.com/grantr/adr/0006-what-v0-1-includes/)).
73
+ - **An append-only audit log** of every change to access ([ADR-0007](https://lattice.alexnodeland.com/grantr/adr/0007-an-append-only-audit-log/)).
74
+ - **Its own model, with OpenFGA-shaped ports,** in memory or in SQL ([ADR-0008](https://lattice.alexnodeland.com/grantr/adr/0008-grantrs-own-model-with-openfga-shaped-ports/)), and a deployment-wide `operator` role ([ADR-0009](https://lattice.alexnodeland.com/grantr/adr/0009-a-deployment-wide-operator-role/)).
75
+ - **Row-level security under the libraries' scoping,** with PostgreSQL keeping each tenant's rows to that tenant ([ADR-0026](https://lattice.alexnodeland.com/grantr/adr/0026-row-level-security-under-the-scoping/)).
76
+
77
+ ## Quick start
78
+
79
+ ```python
80
+ from artifactr.workspace import ACTIONS as ARTIFACTR
81
+ from grantr import Grants, InMemoryStorage, Resource, User
82
+
83
+ grants = Grants(InMemoryStorage(), catalogs=[ARTIFACTR])
84
+ await grants.first_operator("root")
85
+ await grants.create_tenant("acme", owner="alice", by=User(id="root"))
86
+ await grants.add_member("acme", User(id="bob"), by=User(id="alice"))
87
+ main = Resource(kind="workspace", workspace="main")
88
+ await grants.grant("acme", "editor", on=main, to=User(id="bob"), by=User(id="alice"))
89
+ assert await grants.check("acme", User(id="bob"), "artifactr:edit_artifact", main)
90
+ ```
91
+
92
+ [Getting started](https://lattice.alexnodeland.com/grantr/getting-started/) goes on to the libraries' hooks, SQL storage and the admin API.
93
+
94
+ ## Documentation
95
+
96
+ grantr's documentation is its section of lattice's site, at **<https://lattice.alexnodeland.com/grantr/>**, built from [`docs/grantr/`](https://github.com/alexnodeland/lattice/tree/main/docs/grantr).
97
+
98
+ - [Getting started](https://lattice.alexnodeland.com/grantr/getting-started/), and the guides: roles and grants, restricted threads and artifacts, service principals and keys, row-level security, and the permission matrix.
99
+ - [Architecture](https://lattice.alexnodeland.com/grantr/architecture/): the model, the checks, the commands and the storage port.
100
+ - [Architecture decision records](https://lattice.alexnodeland.com/grantr/adr/): why each part is the way it is.
101
+ - [RFCs](https://lattice.alexnodeland.com/grantr/rfcs/): the v0.1 plan, its phases and what each waits on.
102
+ - [Identity and access](https://lattice.alexnodeland.com/rfcs/0001-grantr-identity-and-access/), lattice's RFC-0001: the design grantr builds.
103
+ - [Brand](https://lattice.alexnodeland.com/grantr/assets/brand/): the mark and its colours.
104
+
105
+ ## Contributing
106
+
107
+ See [CONTRIBUTING.md](https://github.com/alexnodeland/lattice/blob/main/CONTRIBUTING.md) for setup, the trunk-based workflow, and the RFC and ADR process.
108
+
109
+ ## License
110
+
111
+ [MIT](https://github.com/alexnodeland/lattice/blob/main/packages/grantr/LICENSE)
grantr-0.1.0/README.md ADDED
@@ -0,0 +1,68 @@
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/grantr/assets/brand/banner-dark.svg">
3
+ <img alt="grantr: identity and access for artifactr, reflexr and portalr." src="https://raw.githubusercontent.com/alexnodeland/lattice/main/docs/grantr/assets/brand/banner-light.svg" width="100%">
4
+ </picture>
5
+
6
+ <p>
7
+ <a href="https://lattice.alexnodeland.com/grantr/"><img alt="Docs" src="https://img.shields.io/badge/docs-lattice.alexnodeland.com%2Fgrantr-6B5300"></a>
8
+ <a href="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml"><img alt="Nightly" src="https://github.com/alexnodeland/lattice/actions/workflows/nightly.yml/badge.svg?branch=main"></a>
9
+ <img alt="Python 3.12, 3.13 and 3.14" src="https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-6B5300">
10
+ <img alt="Coverage: 100%" src="https://img.shields.io/badge/coverage-100%25-6B5300">
11
+ <img alt="Typed: pyright strict" src="https://img.shields.io/badge/typed-pyright%20strict-6B5300">
12
+ <a href="https://github.com/alexnodeland/lattice/blob/main/packages/grantr/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-6B5300"></a>
13
+ </p>
14
+
15
+ **grantr** is the identity and access package for [artifactr](https://lattice.alexnodeland.com/artifactr/), [reflexr](https://lattice.alexnodeland.com/reflexr/) and [portalr](https://lattice.alexnodeland.com/portalr/): tenants, their members, groups, roles and service principals, and the grants that bind them to tenants, workspaces, threads, artifacts and rules. The libraries ask it, through their own hooks, before every command and read. It imports neither library, and neither imports it.
16
+
17
+ > **Status:** v0.1's scope is built: the model, built-in and custom roles, groups, service principals and keys, checks, the libraries' hooks, restricted threads and artifacts, the audit log, in-memory and SQL storage with row-level security (the `sql`, `sqlite` and `postgres` extras), the admin router (the `fastapi` extra) and telemetry. stackr's template wires it in by default, so each library asks it before every command and read.
18
+
19
+ ## Why
20
+
21
+ Before grantr, any user of a tenant could do anything in every workspace of that tenant. The libraries' `authorize` hook was asked when a workspace was opened, and never heard what the actor was about to do, so a viewer couldn't be kept from editing. A user had exactly one tenant, set in their token, and nothing stored members, groups or roles. [lattice RFC-0001](https://lattice.alexnodeland.com/rfcs/0001-grantr-identity-and-access/) designs access once, for every surface, and grantr is where its model is built.
22
+
23
+ ## What it does
24
+
25
+ - **Check every command and read.** The libraries' hook hears the action, such as `artifactr:edit_artifact`, and asks grantr before each command and read, on every surface ([ADR-0001](https://lattice.alexnodeland.com/grantr/adr/0001-the-hook-hears-the-action/)).
26
+ - **Grant down to threads and artifacts,** and hide a restricted one from everyone without a role on it ([ADR-0002](https://lattice.alexnodeland.com/grantr/adr/0002-grants-down-to-threads-and-artifacts/), [ADR-0003](https://lattice.alexnodeland.com/grantr/adr/0003-visible-to-the-workspace-unless-restricted/)).
27
+ - **Act for a user.** An agent's turn is checked as the person whose message started it ([ADR-0004](https://lattice.alexnodeland.com/grantr/adr/0004-an-agent-turn-acts-for-its-user/)).
28
+ - **Own membership.** A user's tenants come from grantr, and each request names its tenant ([ADR-0005](https://lattice.alexnodeland.com/grantr/adr/0005-a-users-tenants-from-grantrs-memberships/)).
29
+ - **Built-in and custom roles, groups and service principals,** with keys shown once, and checks that deny by default ([ADR-0006](https://lattice.alexnodeland.com/grantr/adr/0006-what-v0-1-includes/)).
30
+ - **An append-only audit log** of every change to access ([ADR-0007](https://lattice.alexnodeland.com/grantr/adr/0007-an-append-only-audit-log/)).
31
+ - **Its own model, with OpenFGA-shaped ports,** in memory or in SQL ([ADR-0008](https://lattice.alexnodeland.com/grantr/adr/0008-grantrs-own-model-with-openfga-shaped-ports/)), and a deployment-wide `operator` role ([ADR-0009](https://lattice.alexnodeland.com/grantr/adr/0009-a-deployment-wide-operator-role/)).
32
+ - **Row-level security under the libraries' scoping,** with PostgreSQL keeping each tenant's rows to that tenant ([ADR-0026](https://lattice.alexnodeland.com/grantr/adr/0026-row-level-security-under-the-scoping/)).
33
+
34
+ ## Quick start
35
+
36
+ ```python
37
+ from artifactr.workspace import ACTIONS as ARTIFACTR
38
+ from grantr import Grants, InMemoryStorage, Resource, User
39
+
40
+ grants = Grants(InMemoryStorage(), catalogs=[ARTIFACTR])
41
+ await grants.first_operator("root")
42
+ await grants.create_tenant("acme", owner="alice", by=User(id="root"))
43
+ await grants.add_member("acme", User(id="bob"), by=User(id="alice"))
44
+ main = Resource(kind="workspace", workspace="main")
45
+ await grants.grant("acme", "editor", on=main, to=User(id="bob"), by=User(id="alice"))
46
+ assert await grants.check("acme", User(id="bob"), "artifactr:edit_artifact", main)
47
+ ```
48
+
49
+ [Getting started](https://lattice.alexnodeland.com/grantr/getting-started/) goes on to the libraries' hooks, SQL storage and the admin API.
50
+
51
+ ## Documentation
52
+
53
+ grantr's documentation is its section of lattice's site, at **<https://lattice.alexnodeland.com/grantr/>**, built from [`docs/grantr/`](https://github.com/alexnodeland/lattice/tree/main/docs/grantr).
54
+
55
+ - [Getting started](https://lattice.alexnodeland.com/grantr/getting-started/), and the guides: roles and grants, restricted threads and artifacts, service principals and keys, row-level security, and the permission matrix.
56
+ - [Architecture](https://lattice.alexnodeland.com/grantr/architecture/): the model, the checks, the commands and the storage port.
57
+ - [Architecture decision records](https://lattice.alexnodeland.com/grantr/adr/): why each part is the way it is.
58
+ - [RFCs](https://lattice.alexnodeland.com/grantr/rfcs/): the v0.1 plan, its phases and what each waits on.
59
+ - [Identity and access](https://lattice.alexnodeland.com/rfcs/0001-grantr-identity-and-access/), lattice's RFC-0001: the design grantr builds.
60
+ - [Brand](https://lattice.alexnodeland.com/grantr/assets/brand/): the mark and its colours.
61
+
62
+ ## Contributing
63
+
64
+ See [CONTRIBUTING.md](https://github.com/alexnodeland/lattice/blob/main/CONTRIBUTING.md) for setup, the trunk-based workflow, and the RFC and ADR process.
65
+
66
+ ## License
67
+
68
+ [MIT](https://github.com/alexnodeland/lattice/blob/main/packages/grantr/LICENSE)
@@ -0,0 +1,142 @@
1
+ [project]
2
+ name = "grantr"
3
+ version = "0.1.0"
4
+ description = "Identity and access for artifactr and reflexr: tenants, members, groups, roles, service principals and grants, checked before every command and read."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.12"
9
+ keywords = [
10
+ "authorization",
11
+ "access-control",
12
+ "rbac",
13
+ "multi-tenant",
14
+ "agents",
15
+ "pydantic",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 2 - Pre-Alpha",
19
+ "Framework :: Pydantic :: 2",
20
+ "Intended Audience :: Developers",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Topic :: Security",
27
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
28
+ "Typing :: Typed",
29
+ ]
30
+ dependencies = [
31
+ "pydantic>=2.13",
32
+ "opentelemetry-api>=1.28",
33
+ ]
34
+
35
+ [[project.authors]]
36
+ name = "Alex Nodeland"
37
+ email = "alex@ournature.studio"
38
+
39
+ [project.optional-dependencies]
40
+ sql = [
41
+ "lattice-core-ai[sql]>=0.1.0",
42
+ "sqlalchemy[asyncio]>=2.1",
43
+ "alembic>=1.20",
44
+ ]
45
+ sqlite = [
46
+ "grantr[sql]",
47
+ "aiosqlite>=0.22",
48
+ ]
49
+ postgres = [
50
+ "grantr[sql]",
51
+ "asyncpg>=0.31",
52
+ ]
53
+ fastapi = [
54
+ "fastapi>=0.141",
55
+ "starlette>=0.46.0",
56
+ ]
57
+
58
+ [project.urls]
59
+ Homepage = "https://lattice.alexnodeland.com/grantr/"
60
+ Documentation = "https://lattice.alexnodeland.com/grantr/"
61
+ Repository = "https://github.com/alexnodeland/lattice"
62
+ Issues = "https://github.com/alexnodeland/lattice/issues"
63
+ Changelog = "https://github.com/alexnodeland/lattice/blob/main/packages/grantr/CHANGELOG.md"
64
+
65
+ [dependency-groups]
66
+ test = [
67
+ "pytest>=9.1",
68
+ "pytest-asyncio>=1.4",
69
+ "pytest-cov>=7.1",
70
+ "pytest-timeout>=2.4",
71
+ "httpx>=0.28",
72
+ "aiosqlite>=0.22",
73
+ "asyncpg>=0.31",
74
+ "opentelemetry-sdk>=1.28",
75
+ "hypothesis>=6.168",
76
+ ]
77
+ lint = [
78
+ "ruff>=0.16",
79
+ "pyright>=1.1.414",
80
+ ]
81
+
82
+ [[dependency-groups.dev]]
83
+ include-group = "test"
84
+
85
+ [[dependency-groups.dev]]
86
+ include-group = "lint"
87
+
88
+ [tool.uv.sources.lattice-core-ai]
89
+ workspace = true
90
+
91
+ [tool.deptry.package_module_name_map]
92
+ lattice-core-ai = "lattice_core"
93
+
94
+ [tool.pyright]
95
+ include = [
96
+ "src",
97
+ "tests",
98
+ ]
99
+ strict = ["src"]
100
+ typeCheckingMode = "standard"
101
+ pythonVersion = "3.12"
102
+ venvPath = "../.."
103
+ venv = ".venv"
104
+
105
+ [tool.pytest.ini_options]
106
+ testpaths = ["tests"]
107
+ addopts = [
108
+ "--strict-markers",
109
+ "--strict-config",
110
+ "-ra",
111
+ ]
112
+ timeout = 60
113
+ timeout_method = "thread"
114
+ asyncio_mode = "auto"
115
+ asyncio_default_fixture_loop_scope = "function"
116
+ filterwarnings = ["error"]
117
+ markers = ["postgres: needs a PostgreSQL database (set GRANTR_TEST_POSTGRES_URL)"]
118
+
119
+ [tool.coverage.run]
120
+ branch = true
121
+ source = ["src/grantr"]
122
+ concurrency = [
123
+ "greenlet",
124
+ "thread",
125
+ ]
126
+
127
+ [tool.coverage.report]
128
+ fail_under = 100
129
+ show_missing = true
130
+ skip_covered = true
131
+ exclude_also = [
132
+ "if TYPE_CHECKING:",
133
+ "@overload",
134
+ 'assert_never\(',
135
+ "raise NotImplementedError",
136
+ '^\s*\.\.\.$',
137
+ 'class .*\(Protocol.*\):',
138
+ ]
139
+
140
+ [build-system]
141
+ requires = ["uv_build>=0.12,<0.13"]
142
+ build-backend = "uv_build"
@@ -0,0 +1,116 @@
1
+ [project]
2
+ name = "grantr"
3
+ version = "0.1.0"
4
+ description = "Identity and access for artifactr and reflexr: tenants, members, groups, roles, service principals and grants, checked before every command and read."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Alex Nodeland", email = "alex@ournature.studio" }]
9
+ requires-python = ">=3.12"
10
+ keywords = ["authorization", "access-control", "rbac", "multi-tenant", "agents", "pydantic"]
11
+ classifiers = [
12
+ "Development Status :: 2 - Pre-Alpha",
13
+ "Framework :: Pydantic :: 2",
14
+ "Intended Audience :: Developers",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3 :: Only",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Topic :: Security",
21
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = [
25
+ "pydantic>=2.13",
26
+ "opentelemetry-api>=1.28",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ sql = ["lattice-core-ai[sql]>=0.1.0", "sqlalchemy[asyncio]>=2.1", "alembic>=1.20"]
31
+ sqlite = ["grantr[sql]", "aiosqlite>=0.22"]
32
+ postgres = ["grantr[sql]", "asyncpg>=0.31"]
33
+ fastapi = ["fastapi>=0.141", "starlette>=0.46.0"]
34
+
35
+ [project.urls]
36
+ Homepage = "https://lattice.alexnodeland.com/grantr/"
37
+ Documentation = "https://lattice.alexnodeland.com/grantr/"
38
+ Repository = "https://github.com/alexnodeland/lattice"
39
+ Issues = "https://github.com/alexnodeland/lattice/issues"
40
+ Changelog = "https://github.com/alexnodeland/lattice/blob/main/packages/grantr/CHANGELOG.md"
41
+
42
+ [dependency-groups]
43
+ test = [
44
+ "pytest>=9.1",
45
+ "pytest-asyncio>=1.4",
46
+ "pytest-cov>=7.1",
47
+ "pytest-timeout>=2.4",
48
+ "httpx>=0.28",
49
+ "aiosqlite>=0.22",
50
+ "asyncpg>=0.31",
51
+ "opentelemetry-sdk>=1.28",
52
+ "hypothesis>=6.168",
53
+ ]
54
+ lint = ["ruff>=0.16", "pyright>=1.1.414"]
55
+ dev = [
56
+ { include-group = "test" },
57
+ { include-group = "lint" },
58
+ ]
59
+
60
+ # In lattice, lattice-core is the checkout's. A built wheel keeps the range in [project] alone.
61
+ [tool.uv.sources]
62
+ lattice-core-ai = { workspace = true }
63
+
64
+ [build-system]
65
+ requires = ["uv_build>=0.12,<0.13"]
66
+ build-backend = "uv_build"
67
+
68
+ # ─── dependencies ─────────────────────────────────────────────────────────────
69
+
70
+ # deptry can't tell from the workspace's editable install which module lattice-core-ai provides.
71
+ [tool.deptry.package_module_name_map]
72
+ lattice-core-ai = "lattice_core"
73
+
74
+ # ─── types ────────────────────────────────────────────────────────────────────
75
+
76
+ [tool.pyright]
77
+ include = ["src", "tests"]
78
+ strict = ["src"]
79
+ typeCheckingMode = "standard"
80
+ pythonVersion = "3.12"
81
+ venvPath = "../.."
82
+ venv = ".venv"
83
+
84
+ # ─── tests and coverage ───────────────────────────────────────────────────────
85
+
86
+ [tool.pytest.ini_options]
87
+ testpaths = ["tests"]
88
+ addopts = ["--strict-markers", "--strict-config", "-ra"]
89
+ # A test that runs for a minute is stuck: fail it, with every thread's stack.
90
+ timeout = 60
91
+ timeout_method = "thread"
92
+ asyncio_mode = "auto"
93
+ asyncio_default_fixture_loop_scope = "function"
94
+ filterwarnings = ["error"]
95
+ markers = [
96
+ "postgres: needs a PostgreSQL database (set GRANTR_TEST_POSTGRES_URL)",
97
+ ]
98
+
99
+ [tool.coverage.run]
100
+ branch = true
101
+ source = ["src/grantr"]
102
+ # SQLAlchemy runs run_sync callables in a greenlet, which coverage follows only when told.
103
+ concurrency = ["greenlet", "thread"]
104
+
105
+ [tool.coverage.report]
106
+ fail_under = 100
107
+ show_missing = true
108
+ skip_covered = true
109
+ exclude_also = [
110
+ "if TYPE_CHECKING:",
111
+ "@overload",
112
+ "assert_never\\(",
113
+ "raise NotImplementedError",
114
+ "^\\s*\\.\\.\\.$",
115
+ "class .*\\(Protocol.*\\):",
116
+ ]
@@ -0,0 +1,84 @@
1
+ """grantr: identity and access for artifactr, reflexr and portalr.
2
+
3
+ Tenants, their members, groups, roles and service principals, and the grants that bind them to
4
+ tenants, workspaces, threads, artifacts and rules. The libraries ask it, through their own hooks,
5
+ before every command and read; it imports neither library, and neither imports it.
6
+
7
+ The model and its rules are in :mod:`grantr.core`, with no I/O; :class:`Grants` answers checks
8
+ and changes access over a storage, such as :class:`InMemoryStorage`. The documentation, with its
9
+ decisions and the plan that builds grantr, is at <https://lattice.alexnodeland.com/grantr/>.
10
+ """
11
+
12
+ from importlib.metadata import version
13
+
14
+ from grantr.core import (
15
+ DEPLOYMENT,
16
+ TENANT,
17
+ Agent,
18
+ AuditEntry,
19
+ AuditEvent,
20
+ AuditFilter,
21
+ AuditType,
22
+ BuiltInRole,
23
+ CatalogView,
24
+ Forbidden,
25
+ Grant,
26
+ Grantee,
27
+ GrantrError,
28
+ Group,
29
+ Invalid,
30
+ Member,
31
+ Membership,
32
+ Resource,
33
+ ResourceKind,
34
+ Role,
35
+ RulePrincipal,
36
+ ServicePrincipal,
37
+ Subject,
38
+ TenantId,
39
+ User,
40
+ UserId,
41
+ Visibility,
42
+ WorkspaceId,
43
+ )
44
+ from grantr.grants import Access, Grants, Hooks, InMemoryStorage, Storage, SubjectOf
45
+
46
+ __version__ = version("grantr")
47
+
48
+ __all__ = [
49
+ "DEPLOYMENT",
50
+ "TENANT",
51
+ "Access",
52
+ "Agent",
53
+ "AuditEntry",
54
+ "AuditEvent",
55
+ "AuditFilter",
56
+ "AuditType",
57
+ "BuiltInRole",
58
+ "CatalogView",
59
+ "Forbidden",
60
+ "Grant",
61
+ "Grantee",
62
+ "GrantrError",
63
+ "Grants",
64
+ "Group",
65
+ "Hooks",
66
+ "InMemoryStorage",
67
+ "Invalid",
68
+ "Member",
69
+ "Membership",
70
+ "Resource",
71
+ "ResourceKind",
72
+ "Role",
73
+ "RulePrincipal",
74
+ "ServicePrincipal",
75
+ "Storage",
76
+ "Subject",
77
+ "SubjectOf",
78
+ "TenantId",
79
+ "User",
80
+ "UserId",
81
+ "Visibility",
82
+ "WorkspaceId",
83
+ "__version__",
84
+ ]
@@ -0,0 +1,123 @@
1
+ """grantr's model, and the rules a check and a grant follow: no I/O, no async.
2
+
3
+ :class:`~grantr.grants.Grants` loads what a decision needs from storage, and decides with these.
4
+ """
5
+
6
+ from grantr.core.audit import (
7
+ AuditEntry,
8
+ AuditEvent,
9
+ AuditFilter,
10
+ AuditType,
11
+ DeploymentTenantCreated,
12
+ Granted,
13
+ GroupDefined,
14
+ GroupDeleted,
15
+ KeyIssued,
16
+ KeyRevoked,
17
+ MemberAdded,
18
+ MemberRemoved,
19
+ Restricted,
20
+ Revoked,
21
+ RoleDefined,
22
+ RoleDeleted,
23
+ TenantCreated,
24
+ TenantDeleted,
25
+ Unrestricted,
26
+ )
27
+ from grantr.core.errors import Forbidden, GrantrError, Invalid
28
+ from grantr.core.grants import Grant, Membership, Visibility, memberships
29
+ from grantr.core.ids import PRINCIPAL_ID, TENANT_ID, PrincipalId, TenantId, UserId, WorkspaceId
30
+ from grantr.core.keys import Key, issue, key_id, verify
31
+ from grantr.core.resources import (
32
+ DEPLOYMENT,
33
+ ITEMS,
34
+ TENANT,
35
+ ListableKind,
36
+ Resource,
37
+ ResourceKind,
38
+ )
39
+ from grantr.core.roles import (
40
+ APPLICATION,
41
+ BUILT_IN,
42
+ GRANTR_ACTIONS,
43
+ MEMBER,
44
+ OPERATOR,
45
+ BuiltInRole,
46
+ Catalog,
47
+ CatalogView,
48
+ Level,
49
+ Role,
50
+ )
51
+ from grantr.core.subjects import (
52
+ Agent,
53
+ Grantee,
54
+ Group,
55
+ Member,
56
+ RulePrincipal,
57
+ ServicePrincipal,
58
+ Subject,
59
+ User,
60
+ )
61
+
62
+ __all__ = [
63
+ "APPLICATION",
64
+ "BUILT_IN",
65
+ "DEPLOYMENT",
66
+ "GRANTR_ACTIONS",
67
+ "ITEMS",
68
+ "MEMBER",
69
+ "OPERATOR",
70
+ "PRINCIPAL_ID",
71
+ "TENANT",
72
+ "TENANT_ID",
73
+ "Agent",
74
+ "AuditEntry",
75
+ "AuditEvent",
76
+ "AuditFilter",
77
+ "AuditType",
78
+ "BuiltInRole",
79
+ "Catalog",
80
+ "CatalogView",
81
+ "DeploymentTenantCreated",
82
+ "Forbidden",
83
+ "Grant",
84
+ "Granted",
85
+ "Grantee",
86
+ "GrantrError",
87
+ "Group",
88
+ "GroupDefined",
89
+ "GroupDeleted",
90
+ "Invalid",
91
+ "Key",
92
+ "KeyIssued",
93
+ "KeyRevoked",
94
+ "Level",
95
+ "ListableKind",
96
+ "Member",
97
+ "MemberAdded",
98
+ "MemberRemoved",
99
+ "Membership",
100
+ "PrincipalId",
101
+ "Resource",
102
+ "ResourceKind",
103
+ "Restricted",
104
+ "Revoked",
105
+ "Role",
106
+ "RoleDefined",
107
+ "RoleDeleted",
108
+ "RulePrincipal",
109
+ "ServicePrincipal",
110
+ "Subject",
111
+ "TenantCreated",
112
+ "TenantDeleted",
113
+ "TenantId",
114
+ "Unrestricted",
115
+ "User",
116
+ "UserId",
117
+ "Visibility",
118
+ "WorkspaceId",
119
+ "issue",
120
+ "key_id",
121
+ "memberships",
122
+ "verify",
123
+ ]