terp-cli 0.28.0__tar.gz → 0.30.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. {terp_cli-0.28.0 → terp_cli-0.30.0}/PKG-INFO +8 -8
  2. {terp_cli-0.28.0 → terp_cli-0.30.0}/pyproject.toml +8 -8
  3. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/__init__.py +27 -1
  4. {terp_cli-0.28.0 → terp_cli-0.30.0}/.gitignore +0 -0
  5. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/_appref.py +0 -0
  6. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/_engine.py +0 -0
  7. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/_output.py +0 -0
  8. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/_subjects.py +0 -0
  9. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/access.py +0 -0
  10. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/apidocs.py +0 -0
  11. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/authz_surface.py +0 -0
  12. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/capabilities.py +0 -0
  13. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/deploy_safety.py +0 -0
  14. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/dev.py +0 -0
  15. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/docker.py +0 -0
  16. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/envfile.py +0 -0
  17. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/envschema.py +0 -0
  18. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/envseams.py +0 -0
  19. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/fmt.py +0 -0
  20. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/grants.py +0 -0
  21. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/jobs.py +0 -0
  22. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/leases.py +0 -0
  23. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/module_roles.py +0 -0
  24. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/openapi.py +0 -0
  25. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/outbox.py +0 -0
  26. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/ports.py +0 -0
  27. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/profiles.py +0 -0
  28. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/py.typed +0 -0
  29. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/routes.py +0 -0
  30. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/scaffold.py +0 -0
  31. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/schema.py +0 -0
  32. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/seed.py +0 -0
  33. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/service_accounts.py +0 -0
  34. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/smoke.py +0 -0
  35. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/users.py +0 -0
  36. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/verify.py +0 -0
  37. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/version.py +0 -0
  38. {terp_cli-0.28.0 → terp_cli-0.30.0}/src/terp/cli/workbench.py +0 -0
@@ -1,19 +1,19 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: terp-cli
3
- Version: 0.28.0
3
+ Version: 0.30.0
4
4
  Summary: Terp command-line tool — inspect, scaffolding, migrations, checks, api-docs.
5
5
  Project-URL: Repository, https://github.com/AITT-NL/terp-framework
6
6
  Project-URL: Changelog, https://github.com/AITT-NL/terp-framework/blob/main/CHANGELOG.md
7
7
  License-Expression: Apache-2.0
8
8
  Requires-Python: >=3.13
9
9
  Requires-Dist: pyyaml>=6.0
10
- Requires-Dist: terp-arch==0.28.0
11
- Requires-Dist: terp-core==0.28.0
12
- Requires-Dist: terp-migrations==0.28.0
10
+ Requires-Dist: terp-arch==0.30.0
11
+ Requires-Dist: terp-core==0.30.0
12
+ Requires-Dist: terp-migrations==0.30.0
13
13
  Provides-Extra: jobs
14
- Requires-Dist: terp-cap-outbox==0.28.0; extra == 'jobs'
15
- Requires-Dist: terp-cap-scheduler-apscheduler==0.28.0; extra == 'jobs'
14
+ Requires-Dist: terp-cap-outbox==0.30.0; extra == 'jobs'
15
+ Requires-Dist: terp-cap-scheduler-apscheduler==0.30.0; extra == 'jobs'
16
16
  Provides-Extra: scheduler
17
- Requires-Dist: terp-cap-scheduler-apscheduler==0.28.0; extra == 'scheduler'
17
+ Requires-Dist: terp-cap-scheduler-apscheduler==0.30.0; extra == 'scheduler'
18
18
  Provides-Extra: worker
19
- Requires-Dist: terp-cap-outbox==0.28.0; extra == 'worker'
19
+ Requires-Dist: terp-cap-outbox==0.30.0; extra == 'worker'
@@ -4,18 +4,18 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "terp-cli"
7
- version = "0.28.0"
7
+ version = "0.30.0"
8
8
  description = "Terp command-line tool — inspect, scaffolding, migrations, checks, api-docs."
9
9
  requires-python = ">=3.13"
10
10
  license = "Apache-2.0"
11
11
  dependencies = [
12
- "terp-core==0.28.0",
12
+ "terp-core==0.30.0",
13
13
  # `terp migrate` delegates to terp-migrations (lazily imported), keeping Alembic
14
14
  # off the path for `terp inspect` / `terp guide` while making migrations work.
15
- "terp-migrations==0.28.0",
15
+ "terp-migrations==0.30.0",
16
16
  # `terp guide rules` projects the live rule registry; terp-arch is imported lazily
17
17
  # (only when that topic is rendered), so it stays off the common `terp guide` path.
18
- "terp-arch==0.28.0",
18
+ "terp-arch==0.30.0",
19
19
  # `terp verify --only env-seams` and `terp smoke` read the compose profiles as data
20
20
  # (anchors and `<<:` merge keys resolved), so both answer without a Docker daemon or
21
21
  # even the `docker` binary. Declared explicitly rather than leant on: every app
@@ -29,11 +29,11 @@ dependencies = [
29
29
  # or a scheduler engine into apps that only use inspect/check/migrate. Deployments can select
30
30
  # one process role, while `jobs` is the convenient complete jobs-process bundle.
31
31
  [project.optional-dependencies]
32
- worker = ["terp-cap-outbox==0.28.0"]
33
- scheduler = ["terp-cap-scheduler-apscheduler==0.28.0"]
32
+ worker = ["terp-cap-outbox==0.30.0"]
33
+ scheduler = ["terp-cap-scheduler-apscheduler==0.30.0"]
34
34
  jobs = [
35
- "terp-cap-outbox==0.28.0",
36
- "terp-cap-scheduler-apscheduler==0.28.0",
35
+ "terp-cap-outbox==0.30.0",
36
+ "terp-cap-scheduler-apscheduler==0.30.0",
37
37
  ]
38
38
 
39
39
  [project.scripts]
@@ -106,6 +106,10 @@ Add a module (the "10-minute module")
106
106
  @router.post("/", response_model=InvoiceRead, status_code=201)
107
107
  def create_invoice(payload: InvoiceCreate, session: SessionDep) -> InvoiceRead:
108
108
  return InvoiceRead.model_validate(_service.create(session, payload))
109
+ Register every route with a route decorator or add_api_route. A plain Starlette route
110
+ (router.add_route), a Starlette WebSocket route or a Mount is served without the
111
+ router's dependencies, so the deny-by-default guard never runs for it, and the boot
112
+ refuses one. A route anyone may call declares route_policy(Policy.public(reason=...)).
109
113
 
110
114
  5) module.py the manifest
111
115
  module = ModuleSpec(name="invoices", router=router, policy=Policy.default())
@@ -434,6 +438,12 @@ Authorization (Policy)
434
438
  or boot fails closed. Grant via the access capability; the caller must clear the
435
439
  min_role floor AND hold the grant.
436
440
  - Route-level extra check: dependencies=[Depends(require_permission(APPROVE))].
441
+ - A module that only READS says so, and the boot holds every route to it:
442
+ ModuleSpec(name="reports", router=router, policy=Policy.default(), read_only=True)
443
+ A route that answers POST / PUT / PATCH / DELETE, or a WebSocket, then refuses the
444
+ boot, on an included sub-router too, unless it is declared @read_only (it computes an
445
+ answer and persists nothing). Undeclared, the module is read-only only until someone
446
+ adds a write route. It is not a Policy: route_policy(...) cannot reopen it.
437
447
  - Authority is always a typed object (Role / Permission), never a bare string — pass the
438
448
  declared constant, not its name. no_adhoc_permission_literals refuses the literal.
439
449
  - Choosing between a role and a permission, and the grant lifecycle: `terp guide permissions`.
@@ -748,6 +758,7 @@ Rows that cannot change, and rows that cannot go (append_only)
748
758
  - The neighbouring guarantees, so you pick the right one:
749
759
  append_only the ROW cannot change after insert
750
760
  @read_only the ROUTE writes nothing, though its verb is unsafe
761
+ ModuleSpec(read_only=True) no ROUTE in the module writes (see: policy)
751
762
  SoftDeleteMixin the row survives its own delete (see: soft-delete)
752
763
  OnDelete.RESTRICT another row cannot be deleted while this one points at it
753
764
  BaseUpdateSchema a concurrent writer cannot lose your edit (OCC, 409)
@@ -1216,6 +1227,13 @@ RESTORES it after. No conftest.py line, no opt-in. Without it a suite goes
1216
1227
  order-dependent: green together, red alone - the sharpest failure mode there is,
1217
1228
  because the green is the wrong answer.
1218
1229
 
1230
+ SIGNING TOKENS NEEDS NO KEY OF YOUR OWN. The same plugin's `terp_signing_key` fixture
1231
+ (autouse, once per session) replaces the development SECRET_KEY with a random 64-character
1232
+ one while the default is in place, and puts the default back afterwards. So a test that
1233
+ calls create_access_token signs with a strong key and pyjwt does not warn. Never write a
1234
+ key into a test file: it is a credential-shaped literal the secret scan flags. A key the
1235
+ environment sets, or one a test sets with monkeypatch, is left as it is.
1236
+
1219
1237
  THE SIX SEAMS. create_app installs six process globals per app; in a test process they
1220
1238
  outlive the app that installed them. Restored automatically -- but installed by you:
1221
1239
 
@@ -1513,6 +1531,14 @@ Using capabilities
1513
1531
  BASE PROFILE — auth, identity, users, groups, access, audit — so authentication,
1514
1532
  accounts, group membership, permission checks and the audit trail are wired before you
1515
1533
  write anything. Install the others you need.
1534
+ - WHO IS CALLING, in words a person reads: take `caller: CallerDep` on the route.
1535
+ from terp.capabilities.identity import CallerDep
1536
+ def pause(name: str, caller: CallerDep, session: SessionDep) -> None:
1537
+ service.pause(session, name, decided_by=caller.name)
1538
+ `caller.name` is a user's email or a service account's name, read from the live row;
1539
+ `caller.id` is the key to store when a record must point at who did it. Never take a
1540
+ "decided by" from the request body (it is whatever the client says), and never build
1541
+ an IdentityService inside a module to look the caller up.
1516
1542
  - SEE WHAT EXISTS BEFORE YOU BUILD IT:
1517
1543
  terp inspect capabilities
1518
1544
  lists every maintained capability, whether this app already has it, the exact
@@ -1535,7 +1561,7 @@ Using capabilities
1535
1561
  permission_enforcer=enforce_permission, discover_capabilities=True)
1536
1562
  - You can always drop to native FastAPI/SQLModel — the same gate rules still apply.
1537
1563
  - Outbound HTTP is a capability concern, never a module concern: importing httpx /
1538
- requests / urllib.request / urllib3 / aiohttp in a module is refused by the
1564
+ httpx2 / requests / urllib.request / urllib3 / aiohttp in a module is refused by the
1539
1565
  no_raw_outbound_http rule — SSRF protection, egress allowlists and timeout policy
1540
1566
  belong behind one declared capability, not scattered per call site. That capability
1541
1567
  is `terp-cap-egress`, and it is a declaration rather than a client you configure at
File without changes
File without changes
File without changes