sirius-framework-mcp 0.1.0

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 (58) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +160 -0
  3. package/dist/grammars/tree-sitter-java.wasm +0 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +247 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/java-parser.d.ts +25 -0
  8. package/dist/java-parser.js +281 -0
  9. package/dist/java-parser.js.map +1 -0
  10. package/dist/prompts/index.d.ts +9 -0
  11. package/dist/prompts/index.js +15 -0
  12. package/dist/prompts/index.js.map +1 -0
  13. package/dist/prompts/workflows.d.ts +27 -0
  14. package/dist/prompts/workflows.js +317 -0
  15. package/dist/prompts/workflows.js.map +1 -0
  16. package/dist/resources/biz/analytics.md +157 -0
  17. package/dist/resources/biz/biz-controller.md +151 -0
  18. package/dist/resources/biz/codelists.md +154 -0
  19. package/dist/resources/biz/entity-triple.md +142 -0
  20. package/dist/resources/biz/importer.md +153 -0
  21. package/dist/resources/biz/isenguard.md +156 -0
  22. package/dist/resources/biz/jobs.md +145 -0
  23. package/dist/resources/biz/processes.md +155 -0
  24. package/dist/resources/biz/storage.md +149 -0
  25. package/dist/resources/biz/tenants.md +159 -0
  26. package/dist/resources/biz/testing.md +127 -0
  27. package/dist/resources/db/composites.md +145 -0
  28. package/dist/resources/db/entities.md +156 -0
  29. package/dist/resources/db/mixing.md +176 -0
  30. package/dist/resources/db/queries.md +178 -0
  31. package/dist/resources/db/refs.md +135 -0
  32. package/dist/resources/index.d.ts +27 -0
  33. package/dist/resources/index.js +68 -0
  34. package/dist/resources/index.js.map +1 -0
  35. package/dist/resources/kernel/async.md +189 -0
  36. package/dist/resources/kernel/commons.md +203 -0
  37. package/dist/resources/kernel/config.md +155 -0
  38. package/dist/resources/kernel/di.md +138 -0
  39. package/dist/resources/kernel/lifecycle.md +146 -0
  40. package/dist/resources/loader.d.ts +9 -0
  41. package/dist/resources/loader.js +17 -0
  42. package/dist/resources/loader.js.map +1 -0
  43. package/dist/resources/web/controllers.md +151 -0
  44. package/dist/resources/web/services.md +136 -0
  45. package/dist/resources/web/templates.md +162 -0
  46. package/dist/tools/index.d.ts +4 -0
  47. package/dist/tools/index.js +3 -0
  48. package/dist/tools/index.js.map +1 -0
  49. package/dist/tools/introspection.d.ts +55 -0
  50. package/dist/tools/introspection.js +233 -0
  51. package/dist/tools/introspection.js.map +1 -0
  52. package/dist/tools/scaffold.d.ts +64 -0
  53. package/dist/tools/scaffold.js +505 -0
  54. package/dist/tools/scaffold.js.map +1 -0
  55. package/dist/workspace.d.ts +37 -0
  56. package/dist/workspace.js +185 -0
  57. package/dist/workspace.js.map +1 -0
  58. package/package.json +41 -0
@@ -0,0 +1,145 @@
1
+ # Jobs Framework
2
+
3
+ The jobs framework provides a system for defining, parameterizing, and executing
4
+ background tasks. Jobs are stateless factories (`JobFactory`) that produce work
5
+ which runs either interactively (in the browser) or as batch processes on
6
+ distributed worker nodes.
7
+
8
+ ## JobFactory Interface
9
+
10
+ `JobFactory` is the top-level interface, annotated with `@AutoRegister` so that
11
+ implementations are automatically discovered. In practice, always subclass
12
+ `BasicJobFactory` which handles parameter collection, validation, and execution
13
+ setup.
14
+
15
+ ```java
16
+ @AutoRegister
17
+ public interface JobFactory extends Named, Priorized {
18
+ String getLabel();
19
+ String getIcon();
20
+ String getDescription();
21
+ List<Parameter<?, ?>> getParameters();
22
+ // ...
23
+ }
24
+ ```
25
+
26
+ ## Class Hierarchy — Choose the Right Base Class
27
+
28
+ The hierarchy branches into two paths: interactive and batch.
29
+
30
+ **Interactive jobs** run synchronously while the user waits:
31
+
32
+ - `BasicJobFactory`
33
+ - `InteractiveJobFactory` — base for all interactive jobs
34
+ - `ReportJobFactory` — produces tabular HTML reports
35
+ - `DoughnutChartJobFactory` — renders a doughnut chart
36
+ - `LinearChartJobFactory` — renders a line chart
37
+ - `TimeseriesChartJobFactory` — renders a time-series chart
38
+ - `PolarAreaChartJobFactory` — renders a polar area chart
39
+
40
+ **Batch jobs** run asynchronously in a distributed process:
41
+
42
+ - `BasicJobFactory`
43
+ - `BatchProcessJobFactory` — creates a `Process` and dispatches work via `DistributedTasks`
44
+ - `SimpleBatchProcessJobFactory` — single `execute(ProcessContext)` method
45
+ - `ImportBatchProcessFactory` — file-based import workflows
46
+ - `ExportBatchProcessFactory` — file-based export workflows
47
+ - `CheckBatchProcessFactory` — data validation/checking workflows
48
+ - `ReportBatchProcessFactory` — long-running report generation
49
+
50
+ **Key rule:** Pick the most specific base class. If your job imports data from a
51
+ file, extend `ImportBatchProcessFactory`, not `SimpleBatchProcessJobFactory`.
52
+
53
+ ## Defining Parameters
54
+
55
+ Override `collectParameters()` to declare the job's inputs:
56
+
57
+ ```java
58
+ @Override
59
+ protected void collectParameters(Consumer<Parameter<?, ?>> parameterCollector) {
60
+ parameterCollector.accept(DATE_RANGE_PARAMETER);
61
+ parameterCollector.accept(ACTIVE_ONLY_PARAMETER);
62
+ }
63
+ ```
64
+
65
+ Common parameter types (all in `sirius.biz.jobs.params`):
66
+
67
+ | Type | Description |
68
+ |------------------------|--------------------------------------------|
69
+ | `StringParameter` | Free-text input |
70
+ | `BooleanParameter` | Checkbox toggle |
71
+ | `EnumParameter` | Dropdown from a Java enum |
72
+ | `EntityParameter` | Autocomplete for a database entity |
73
+ | `LocalDateParameter` | Date picker |
74
+ | `DateRangeParameter` | Date range selector |
75
+ | `CodeListParameter` | Selection from a code list |
76
+ | `FileParameter` | File upload (references Layer 2 blobs) |
77
+ | `IntParameter` | Integer input |
78
+ | `SelectStringParameter`| Dropdown from a fixed list of strings |
79
+
80
+ Parameters are built using a fluent API:
81
+
82
+ ```java
83
+ private static final Parameter<String, StringParameter> NAME_PARAM =
84
+ new StringParameter("name", "$MyJob.name")
85
+ .withDescription("$MyJob.name.help")
86
+ .markRequired()
87
+ .build();
88
+ ```
89
+
90
+ ## SimpleBatchProcessJobFactory
91
+
92
+ The simplest way to create a batch job. Override `execute(ProcessContext)`:
93
+
94
+ ```java
95
+ @Register
96
+ public class MyCleanupJob extends SimpleBatchProcessJobFactory {
97
+
98
+ @Override
99
+ protected String getLabel() { return "Cleanup old records"; }
100
+
101
+ @Override
102
+ protected void collectParameters(Consumer<Parameter<?, ?>> parameterCollector) {
103
+ parameterCollector.accept(MAX_AGE_PARAMETER);
104
+ }
105
+
106
+ @Override
107
+ protected void execute(ProcessContext process) throws Exception {
108
+ int maxAge = process.getParameter(MAX_AGE_PARAMETER).orElse(30);
109
+ // do work, use process.log() to report progress
110
+ }
111
+
112
+ @Override
113
+ public String getCategory() {
114
+ return StandardCategories.SYSTEM_ADMINISTRATION;
115
+ }
116
+ }
117
+ ```
118
+
119
+ **Important:** Override `execute(ProcessContext)`, not `executeInBackground()`.
120
+ The process context is already set up for you.
121
+
122
+ ## Categories
123
+
124
+ Group jobs in the UI using `StandardCategories`:
125
+
126
+ - `StandardCategories.MISC` — miscellaneous
127
+ - `StandardCategories.SYSTEM_ADMINISTRATION` — system admin tasks
128
+ - `StandardCategories.MONITORING` — monitoring jobs
129
+ - `StandardCategories.USERS_AND_TENANTS` — user/tenant management
130
+
131
+ Or define your own category string constant (prefix with `$` for NLS lookup).
132
+
133
+ ## Common Mistakes
134
+
135
+ 1. **Storing state in the factory** — `JobFactory` is a singleton. Never store
136
+ per-execution state in fields. Use `ProcessContext` or a `BatchJob` subclass.
137
+
138
+ 2. **Wrong base class** — Using `SimpleBatchProcessJobFactory` for an import that
139
+ should extend `ImportBatchProcessFactory` loses file handling, progress tracking,
140
+ and error reporting that the more specific class provides.
141
+
142
+ 3. **Missing `getCategory()`** — Jobs without a category are hard to find in the UI.
143
+
144
+ 4. **Forgetting required permissions** — Override `getRequiredPermissions()` to
145
+ restrict who can run the job. Without it, any logged-in user can trigger it.
@@ -0,0 +1,155 @@
1
+ # Processes
2
+
3
+ The process framework provides a way to track, log, and monitor long-running
4
+ background operations. Processes are stored in Elasticsearch and provide a
5
+ real-time UI for viewing progress, logs, and outputs.
6
+
7
+ ## The Processes Service
8
+
9
+ `Processes` is the central service, registered with framework flag `biz.processes`:
10
+
11
+ ```java
12
+ @Register(classes = Processes.class, framework = Processes.FRAMEWORK_PROCESSES)
13
+ public class Processes { ... }
14
+ ```
15
+
16
+ Enable it in your configuration:
17
+
18
+ ```hocon
19
+ sirius.frameworks {
20
+ biz.processes = true
21
+ }
22
+ ```
23
+
24
+ ## Two Types of Processes
25
+
26
+ ### Normal Processes
27
+
28
+ Created for a finite task, run to completion, then terminate:
29
+
30
+ ```java
31
+ @Part
32
+ private Processes processes;
33
+
34
+ public void runExport() {
35
+ String processId = processes.createProcess(
36
+ "export", // type (for filtering)
37
+ "Export Products", // title shown in UI
38
+ "fa-solid fa-download", // icon
39
+ UserContext.getCurrentUser(), // owning user
40
+ PersistencePeriod.THREE_MONTHS, // how long to keep
41
+ Map.of("format", "csv") // context data
42
+ );
43
+
44
+ processes.execute(processId, process -> {
45
+ // process is a ProcessContext
46
+ process.log(ProcessLog.info().withMessage("Starting export..."));
47
+ // do work
48
+ process.addTiming("products", watchElapsed);
49
+ process.forceUpdateState("Exported 500 products");
50
+ });
51
+ }
52
+ ```
53
+
54
+ - `execute(processId, consumer)` — runs the consumer and marks the process as
55
+ completed (or errored) when done.
56
+ - `partiallyExecute(processId, consumer)` — runs the consumer but leaves the
57
+ process open for further work (even from other nodes).
58
+
59
+ ### Standby Processes
60
+
61
+ Long-lived processes for recurring background activity (e.g., a webhook receiver
62
+ that logs errors):
63
+
64
+ ```java
65
+ processes.executeInStandbyProcess(
66
+ "webhook-receiver", // type
67
+ () -> "Webhook Receiver", // title supplier
68
+ "fa-solid fa-satellite-dish", // icon
69
+ () -> "Processing incoming webhooks...", // state supplier
70
+ process -> {
71
+ // This runs each time the standby process is invoked
72
+ process.log(ProcessLog.warn().withMessage("Received invalid payload"));
73
+ }
74
+ );
75
+ ```
76
+
77
+ Standby processes are created on demand and reused. The system periodically cleans
78
+ up old log entries to prevent unbounded growth.
79
+
80
+ ## ProcessContext — The Client API
81
+
82
+ Inside `execute()` or `partiallyExecute()`, you interact with a `ProcessContext`:
83
+
84
+ | Method | Description |
85
+ |-----------------------------------|----------------------------------------------|
86
+ | `log(ProcessLog)` | Writes a log entry (info, warn, error) |
87
+ | `addTiming(counter, millis)` | Increments a named performance counter |
88
+ | `addDebugTiming(counter, millis)` | Counter visible only when debugging enabled |
89
+ | `incCounter(counter)` | Increment a counter by one |
90
+ | `forceUpdateState(text)` | Updates the process state text immediately |
91
+ | `updateTitle(text)` | Changes the process title |
92
+ | `addOutput(ProcessOutput)` | Adds a structured output (table, chart) |
93
+ | `getProcessId()` | Returns the process ID |
94
+ | `isActive()` | Returns false if the process was cancelled |
95
+
96
+ ### Structured Outputs
97
+
98
+ Processes can produce tables and charts, not just log lines:
99
+
100
+ ```java
101
+ TableOutput table = process.addTable("results", "Results", columns);
102
+ table.addRow("product-1", "Widget", "42");
103
+
104
+ ChartOutput chart = process.addChart("timeline", "Timeline");
105
+ ```
106
+
107
+ ## Process Entity
108
+
109
+ `Process` is an Elasticsearch entity gated by `@Framework(Processes.FRAMEWORK_PROCESSES)`.
110
+ It stores:
111
+ - Process metadata (type, title, icon, user, tenant)
112
+ - State (RUNNING, STANDBY, TERMINATED, CANCELED, ERRORED)
113
+ - Counters and timings
114
+ - Context map (arbitrary key-value pairs)
115
+ - Persistence period (how long to retain)
116
+
117
+ `ProcessLog` is a separate Elasticsearch entity for individual log entries,
118
+ also gated by `@Framework(Processes.FRAMEWORK_PROCESSES)`.
119
+
120
+ ## Layered Cache Architecture
121
+
122
+ Because Elasticsearch has a ~1-second write-visibility delay, `Processes` uses a
123
+ two-level cache:
124
+
125
+ 1. **First-level cache** — very short-lived, for direct modifications on the same node.
126
+ 2. **Second-level cache** — coherent cache for reading "static" process data
127
+ (context, user, title) across nodes.
128
+
129
+ Standby processes have their own coherent cache since they are long-lived and few
130
+ in number.
131
+
132
+ ## PersistencePeriod
133
+
134
+ Controls how long a completed process is retained before automatic cleanup:
135
+
136
+ - `PersistencePeriod.ONE_DAY`
137
+ - `PersistencePeriod.TWO_WEEKS`
138
+ - `PersistencePeriod.THREE_MONTHS`
139
+ - `PersistencePeriod.SIX_MONTHS`
140
+ - `PersistencePeriod.ONE_YEAR`
141
+ - `PersistencePeriod.FOREVER`
142
+
143
+ ## Common Mistakes
144
+
145
+ 1. **Using `execute()` when `partiallyExecute()` is needed** — If multiple nodes
146
+ or tasks contribute to one process, `execute()` will prematurely mark it as done.
147
+
148
+ 2. **Logging too much** — Every `ProcessLog` is an Elasticsearch document.
149
+ Use `addTiming()` for high-frequency counters instead of logging each item.
150
+
151
+ 3. **Ignoring `isActive()`** — Always check `process.isActive()` in long loops.
152
+ If the user cancels, you should stop promptly.
153
+
154
+ 4. **Forgetting the framework flag** — Without `biz.processes = true` in the
155
+ config, the `Processes` service will not load and `@Part Processes` will be null.
@@ -0,0 +1,149 @@
1
+ # Storage
2
+
3
+ The storage module provides a three-layer architecture for managing files and
4
+ binary content. Each layer serves a different purpose and can be configured
5
+ independently.
6
+
7
+ ## Layer 1 — Physical Storage
8
+
9
+ Layer 1 handles the raw byte storage. It abstracts over different storage backends:
10
+
11
+ - **`FSObjectStorageSpace`** — local filesystem storage (development, small deployments)
12
+ - **`S3ObjectStorageSpace`** — S3-compatible object storage (production)
13
+
14
+ Configuration is done per "space" in HOCON:
15
+
16
+ ```hocon
17
+ storage.layer1 {
18
+ spaces {
19
+ default {
20
+ engine = "fs"
21
+ basePath = "/data/storage"
22
+ }
23
+ documents {
24
+ engine = "s3"
25
+ bucketName = "my-bucket"
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ Key classes:
32
+ - `ObjectStorage` — service for accessing storage spaces
33
+ - `ObjectStorageSpace` — a configured storage area
34
+ - `FileHandle` — represents a downloaded file with metadata
35
+
36
+ ## Layer 2 — Metadata and Blobs
37
+
38
+ Layer 2 adds metadata, versioning, and variant management on top of Layer 1.
39
+ This is the primary API for working with files in application code.
40
+
41
+ ### Core Concepts
42
+
43
+ - **`BlobStorageSpace`** — a logical container for blobs (configured per space)
44
+ - **`Blob`** — a file with metadata (name, size, content type, tenant, etc.)
45
+ - **`BlobContainer`** — groups related blobs (e.g., attachments for an entity)
46
+ - **`Directory`** — optional directory structure within a space
47
+
48
+ ### JDBC vs MongoDB
49
+
50
+ Layer 2 metadata can be stored in either database:
51
+
52
+ | Component | JDBC | MongoDB |
53
+ |-----------------|-------------------------|--------------------------|
54
+ | Storage class | `SQLBlobStorage` | `MongoBlobStorage` |
55
+ | Blob entity | `SQLBlob` | `MongoBlob` |
56
+ | Directory | `SQLDirectory` | `MongoDirectory` |
57
+ | Framework flag | `biz.storage-blob-jdbc` | `biz.storage-blob-mongo` |
58
+
59
+ ### Referencing Blobs from Entities
60
+
61
+ Use `BlobHardRef` or `BlobSoftRef` in entity fields:
62
+
63
+ ```java
64
+ // Hard ref — blob is deleted when the entity is deleted
65
+ private final BlobHardRef logo =
66
+ new BlobHardRef(this, "logo", "tenant-logos");
67
+
68
+ // Soft ref — blob survives entity deletion
69
+ private final BlobSoftRef attachment =
70
+ new BlobSoftRef(this, "attachment", "documents");
71
+ ```
72
+
73
+ ### Working with Blobs
74
+
75
+ ```java
76
+ @Part
77
+ private BlobStorage blobStorage;
78
+
79
+ // Upload
80
+ BlobStorageSpace space = blobStorage.getSpace("documents");
81
+ Blob blob = space.createBlob("report.pdf", "application/pdf", tenantId);
82
+ try (OutputStream out = blob.createOutputStream()) {
83
+ // write content
84
+ }
85
+
86
+ // Download
87
+ try (InputStream in = blob.createInputStream()) {
88
+ // read content
89
+ }
90
+ ```
91
+
92
+ ## Layer 3 — Virtual File System (VFS)
93
+
94
+ Layer 3 provides a unified directory/file tree view over Layer 2 spaces and other
95
+ sources. It powers the file manager UI and FTP/WebDAV access.
96
+
97
+ ### VirtualFile
98
+
99
+ `VirtualFile` is the abstraction for both files and directories:
100
+
101
+ ```java
102
+ @Part
103
+ private VirtualFileSystem vfs;
104
+
105
+ VirtualFile root = vfs.root();
106
+ VirtualFile docs = root.resolve("documents");
107
+ for (VirtualFile child : docs.children()) {
108
+ if (child.isDirectory()) {
109
+ // traverse
110
+ } else {
111
+ // access file content via child.createInputStream()
112
+ }
113
+ }
114
+ ```
115
+
116
+ ### VFS Roots
117
+
118
+ The VFS tree is assembled from `VFSRoot` implementations. Each root contributes
119
+ a subtree (e.g., one root for tenant documents, another for shared templates).
120
+ Custom roots are registered via `@Register(classes = VFSRoot.class)`.
121
+
122
+ ## Framework Flags
123
+
124
+ Storage requires multiple framework flags depending on which layers and backends
125
+ you use:
126
+
127
+ ```hocon
128
+ sirius.frameworks {
129
+ biz.storage = true # Core storage (required)
130
+ biz.storage-blob-jdbc = true # Layer 2 metadata in JDBC
131
+ biz.storage-blob-mongo = false # Layer 2 metadata in MongoDB
132
+ biz.storage-replication-jdbc = false # Replication tasks in JDBC
133
+ biz.storage-replication-mongo = false # Replication tasks in MongoDB
134
+ }
135
+ ```
136
+
137
+ ## Common Mistakes
138
+
139
+ 1. **Wrong ref type** — Using `BlobHardRef` when the blob should outlive the entity
140
+ (e.g., shared documents). Use `BlobSoftRef` if the blob has independent lifecycle.
141
+
142
+ 2. **Missing framework flags** — `biz.storage` alone is not enough. You also need
143
+ `biz.storage-blob-jdbc` or `biz.storage-blob-mongo` for Layer 2 to work.
144
+
145
+ 3. **Not closing streams** — Always close `InputStream` and `OutputStream` from blob
146
+ operations. Unclosed streams leak file handles and may corrupt uploads.
147
+
148
+ 4. **Ignoring variants** — Layer 2 supports automatic variant generation (e.g.,
149
+ thumbnails). Configure variants per space rather than generating them manually.
@@ -0,0 +1,159 @@
1
+ # Tenants
2
+
3
+ The tenants module provides multi-tenant user management. Every piece of business
4
+ data belongs to a tenant, and every user belongs to a tenant. The framework enforces
5
+ tenant isolation automatically.
6
+
7
+ ## Core Interfaces
8
+
9
+ ### Tenant<I>
10
+
11
+ The database-independent interface for a tenant (company/organization):
12
+
13
+ ```java
14
+ public interface Tenant<I extends Serializable>
15
+ extends Entity, Transformable, Traced, Journaled, RateLimitedEntity, PerformanceFlagged {
16
+
17
+ Mapping TENANT_DATA = Mapping.named("tenantData");
18
+ TenantData getTenantData();
19
+ boolean hasPermission(String permission);
20
+ Set<String> getPermissions();
21
+ }
22
+ ```
23
+
24
+ ### UserAccount<I, T>
25
+
26
+ The database-independent interface for a user account within a tenant:
27
+
28
+ ```java
29
+ public interface UserAccount<I extends Serializable, T extends Tenant<I>>
30
+ extends Entity, Transformable, Traced, Journaled {
31
+
32
+ Mapping USER_ACCOUNT_DATA = Mapping.named("userAccountData");
33
+ UserAccountData getUserAccountData();
34
+ BaseEntityRef<I, T> getTenant();
35
+ }
36
+ ```
37
+
38
+ ## TenantData and UserAccountData
39
+
40
+ All tenant fields live in the `TenantData` composite, shared between SQL and Mongo
41
+ implementations. Similarly, user fields live in `UserAccountData`. This avoids
42
+ field duplication across implementations.
43
+
44
+ Key fields in `TenantData`:
45
+ - `name` — display name of the tenant
46
+ - `accountNumber` — unique business identifier
47
+ - `address` — embedded `InternationalAddressData` composite
48
+ - `packageData` — permissions/features assigned to this tenant
49
+
50
+ Key fields in `UserAccountData`:
51
+ - `person` — embedded `PersonData` composite (name, salutation, email)
52
+ - `login` — embedded `LoginData` composite (username, password hash)
53
+ - `permissions` — assigned permissions/roles
54
+
55
+ ## JDBC and MongoDB Implementations
56
+
57
+ | Interface | JDBC Implementation | Mongo Implementation |
58
+ |------------------|------------------------|--------------------------|
59
+ | `Tenant<I>` | `SQLTenant` | `MongoTenant` |
60
+ | `UserAccount` | `SQLUserAccount` | `MongoUserAccount` |
61
+
62
+ These follow the entity triple pattern (see entity-triple resource). Both are
63
+ gated behind framework flags:
64
+ - `biz.tenants-jdbc` for SQL implementations
65
+ - `biz.tenants-mongo` for MongoDB implementations
66
+
67
+ ## Tenant-Aware Entities
68
+
69
+ Entities that belong to a tenant extend `SQLTenantAware` or `MongoTenantAware`:
70
+
71
+ ```java
72
+ public class SQLProduct extends SQLTenantAware {
73
+ // Automatically gets a tenant reference field
74
+ // Tenant is auto-filled on create via fillWithCurrentTenant()
75
+ }
76
+ ```
77
+
78
+ `SQLTenantAware` extends `BizEntity` and adds:
79
+ - A `tenant` field (`SQLEntityRef<SQLTenant>`) — write-once, reject-on-delete
80
+ - `fillWithCurrentTenant()` — fills the tenant from the current session
81
+ - `assertSameTenant()` — validates that a referenced entity belongs to the same tenant
82
+
83
+ The Mongo equivalent (`MongoTenantAware`) works identically with `MongoRef`.
84
+
85
+ ## Tenants Service
86
+
87
+ The `Tenants<I, T, U>` service provides tenant operations:
88
+
89
+ ```java
90
+ @Part
91
+ private Tenants<?, ?, ?> tenants;
92
+
93
+ // Get current tenant
94
+ Tenant<?> currentTenant = tenants.getRequiredTenant();
95
+
96
+ // Check if current user is in the system tenant
97
+ boolean isSystem = tenants.isCurrentTenantSystemTenant();
98
+
99
+ // Filter a query to the current tenant
100
+ SmartQuery<SQLProduct> query = tenants.forCurrentTenant(oma.select(SQLProduct.class));
101
+ ```
102
+
103
+ Concrete implementations: `SQLTenants` (JDBC) and `MongoTenants` (MongoDB).
104
+
105
+ ## TenantUserManager
106
+
107
+ `TenantUserManager` is the `UserManager` implementation for the tenants module.
108
+ It bridges the web security framework with the tenant/user database:
109
+
110
+ - Authenticates users via login credentials
111
+ - Resolves permissions from user roles + tenant package
112
+ - Provides session management
113
+ - Supports "spy" mode (system tenant admins can act as another tenant)
114
+
115
+ Key permissions:
116
+ - `TenantUserManager.PERMISSION_SYSTEM_TENANT_MEMBER` — user belongs to the system tenant
117
+ - `TenantUserManager.PERMISSION_SYSTEM_ADMINISTRATOR` — user is a system admin
118
+ - `Tenant.PERMISSION_SYSTEM_TENANT` — flag on the tenant entity itself
119
+
120
+ ## Permission Model
121
+
122
+ Permissions flow from multiple sources:
123
+
124
+ 1. **Tenant package** — `PackageData` on the tenant defines which features are enabled
125
+ 2. **User roles** — roles assigned to the user, each granting a set of permissions
126
+ 3. **Role mapping** — configured in HOCON, maps role names to permission sets
127
+ 4. **System tenant flag** — system tenant users get additional administrative permissions
128
+
129
+ ```hocon
130
+ security.tenantPermissions {
131
+ admin = ["permission-manage-users", "permission-manage-products"]
132
+ viewer = ["permission-view-products"]
133
+ }
134
+ ```
135
+
136
+ ## System Tenant
137
+
138
+ One tenant is designated as the "system tenant" (configured by ID). Users in the
139
+ system tenant can:
140
+ - Manage all other tenants
141
+ - "Spy" on other tenants (act as if they belong to another tenant)
142
+ - Access system administration features
143
+
144
+ The system tenant flag is checked via `Tenant.hasPermission(Tenant.PERMISSION_SYSTEM_TENANT)`.
145
+
146
+ ## Common Mistakes
147
+
148
+ 1. **Not using `findForTenant()`** — Using `find()` in controllers allows access
149
+ to entities from other tenants. Always use `findForTenant()` for tenant-scoped data.
150
+
151
+ 2. **Forgetting to enable the framework** — Without `biz.tenants-jdbc = true` or
152
+ `biz.tenants-mongo = true`, tenant entities are not registered.
153
+
154
+ 3. **Manual tenant assignment** — Do not set the tenant reference manually.
155
+ `SQLTenantAware.fillWithCurrentTenant()` is called automatically during entity
156
+ creation. Manual assignment can cause tenant mismatch errors.
157
+
158
+ 4. **Confusing tenant permissions with user permissions** — `Tenant.hasPermission()`
159
+ checks tenant-level features. User permissions are checked via `UserInfo.hasPermission()`.