stigmem-plugin-multi-tenant 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.
@@ -0,0 +1,70 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ .eggs/
7
+ dist/
8
+ build/
9
+ .venv/
10
+ venv/
11
+ .uv/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .pytest_cache/
15
+ htmlcov/
16
+ .coverage
17
+ coverage.xml
18
+ *.cover
19
+ coverage/
20
+
21
+ # Node / pnpm
22
+ node_modules/
23
+ .next/
24
+ .turbo/
25
+ .jscpd/
26
+ .pnpm-store/
27
+ dist/
28
+ *.tsbuildinfo
29
+ apps/dashboard/coverage/
30
+ adapters/mcp/coverage/
31
+ sdks/stigmem-ts/coverage/
32
+
33
+ # Environment
34
+ .env
35
+ .env.local
36
+ .env.*.local
37
+
38
+ # Release signing keys — public keys are attached to GitHub Releases only;
39
+ # private keys must NEVER be committed (use offline storage).
40
+ stigmem-release-signing-key.asc
41
+ stigmem-release-signing-key*.asc
42
+ *private*signing*key*.asc
43
+ *secret*signing*key*.asc
44
+
45
+ # IDE
46
+ .vscode/
47
+ .idea/
48
+ *.swp
49
+ *.swo
50
+
51
+ # OS
52
+ .DS_Store
53
+ Thumbs.db
54
+
55
+ # Local Codex project instructions
56
+ AGENTS.md
57
+
58
+ # Docker
59
+ *.log
60
+
61
+ # Stigmem runtime (DB and logs are runtime state, not source)
62
+ data/*.db
63
+ data/*.db-shm
64
+ data/*.db-wal
65
+ stigmem.db
66
+ stigmem.db-shm
67
+ stigmem.db-wal
68
+ logs/
69
+ # Eval results (CI-generated artifacts)
70
+ eval/results/
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: stigmem-plugin-multi-tenant
3
+ Version: 0.1.0
4
+ Summary: Experimental multi-tenant isolation plugin for Stigmem.
5
+ Project-URL: Homepage, https://github.com/eidetic-labs/stigmem
6
+ Project-URL: Documentation, https://github.com/eidetic-labs/stigmem/tree/main/features/multi-tenant
7
+ Project-URL: Repository, https://github.com/eidetic-labs/stigmem
8
+ Project-URL: Issues, https://github.com/eidetic-labs/stigmem/issues
9
+ Author-email: Eidetic Labs <oss@eidetic-labs.ai>
10
+ License: Apache-2.0
11
+ Keywords: authorization,multi-tenant,plugins,stigmem,tenant-isolation
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: pydantic<3,>=2
21
+ Requires-Dist: stigmem-node<1.0.0,>=0.9.0a8
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Stigmem Multi-Tenant Plugin
25
+
26
+ Experimental multi-tenant scoping plugin for Stigmem.
27
+
28
+ This package provides the `stigmem-plugin-multi-tenant` source package for
29
+ alpha validation. It registers through the `stigmem.plugins` entry point group
30
+ and is loaded by `stigmem-node` only when explicitly installed and configured by
31
+ an operator.
32
+
33
+ ## Status
34
+
35
+ Multi-tenant scoping remains experimental. Installing this package does not add
36
+ shared-node readiness to the supported default surface. Default installs
37
+ collapse callers into the `default` tenant unless the plugin is registered and
38
+ `STIGMEM_MULTI_TENANT_ENABLED=true`.
39
+
40
+ The package metadata is publication-shaped for the plugin readiness track, but
41
+ registry publication remains on hold until dry-run evidence and maintainer
42
+ clearance are recorded. See the feature record under `features/multi-tenant/`
43
+ for the current status, evidence, and security notes.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install --pre stigmem-node==0.9.0a8 stigmem-plugin-multi-tenant==0.1.0
49
+ ```
50
+
51
+ ## Enable
52
+
53
+ Set the plugin gate environment variable to opt in:
54
+
55
+ ```bash
56
+ export STIGMEM_MULTI_TENANT_ENABLED=1
57
+ ```
58
+
59
+ The default install is inert; multi-tenant hook behavior only activates when
60
+ the package is installed, discovered through the `stigmem.plugins` entry point,
61
+ and the operator enables the gate. Until then callers continue to collapse into
62
+ the core `default` tenant boundary.
63
+
64
+ ## Disable
65
+
66
+ Unset the plugin gate environment variable, or set it to any value other than
67
+ `1`, `true`, `yes`, or `on`:
68
+
69
+ ```bash
70
+ unset STIGMEM_MULTI_TENANT_ENABLED
71
+ ```
72
+
73
+ The plugin returns to inert state at the next process start. No data migration
74
+ is required; core tenant, scope, and audit isolation continues to hold.
75
+
76
+ ## Test
77
+
78
+ From a Stigmem repository checkout with development dependencies installed:
79
+
80
+ ```bash
81
+ uv run pytest node/tests/plugins/test_multi_tenant_plugin_scaffold.py \
82
+ node/tests/plugins/test_multi_tenant_plugin_validation.py
83
+ ```
84
+
85
+ The package itself ships no separate test tree; upstream plugin validation
86
+ lives in `node/tests/plugins/`.
87
+
88
+ ## Uninstall
89
+
90
+ ```bash
91
+ pip uninstall stigmem-plugin-multi-tenant
92
+ ```
93
+
94
+ Removing the package is sufficient. The gate environment variable becomes moot
95
+ once the entry point is no longer discoverable.
96
+
97
+ ## Project Links
98
+
99
+ - Repository: <https://github.com/eidetic-labs/stigmem>
100
+ - Feature record: <https://github.com/eidetic-labs/stigmem/tree/main/features/multi-tenant>
101
+ - Plugin source: <https://github.com/eidetic-labs/stigmem/tree/main/experimental/multi-tenant>
102
+ - Issue tracker: <https://github.com/eidetic-labs/stigmem/issues>
@@ -0,0 +1,79 @@
1
+ # Stigmem Multi-Tenant Plugin
2
+
3
+ Experimental multi-tenant scoping plugin for Stigmem.
4
+
5
+ This package provides the `stigmem-plugin-multi-tenant` source package for
6
+ alpha validation. It registers through the `stigmem.plugins` entry point group
7
+ and is loaded by `stigmem-node` only when explicitly installed and configured by
8
+ an operator.
9
+
10
+ ## Status
11
+
12
+ Multi-tenant scoping remains experimental. Installing this package does not add
13
+ shared-node readiness to the supported default surface. Default installs
14
+ collapse callers into the `default` tenant unless the plugin is registered and
15
+ `STIGMEM_MULTI_TENANT_ENABLED=true`.
16
+
17
+ The package metadata is publication-shaped for the plugin readiness track, but
18
+ registry publication remains on hold until dry-run evidence and maintainer
19
+ clearance are recorded. See the feature record under `features/multi-tenant/`
20
+ for the current status, evidence, and security notes.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install --pre stigmem-node==0.9.0a8 stigmem-plugin-multi-tenant==0.1.0
26
+ ```
27
+
28
+ ## Enable
29
+
30
+ Set the plugin gate environment variable to opt in:
31
+
32
+ ```bash
33
+ export STIGMEM_MULTI_TENANT_ENABLED=1
34
+ ```
35
+
36
+ The default install is inert; multi-tenant hook behavior only activates when
37
+ the package is installed, discovered through the `stigmem.plugins` entry point,
38
+ and the operator enables the gate. Until then callers continue to collapse into
39
+ the core `default` tenant boundary.
40
+
41
+ ## Disable
42
+
43
+ Unset the plugin gate environment variable, or set it to any value other than
44
+ `1`, `true`, `yes`, or `on`:
45
+
46
+ ```bash
47
+ unset STIGMEM_MULTI_TENANT_ENABLED
48
+ ```
49
+
50
+ The plugin returns to inert state at the next process start. No data migration
51
+ is required; core tenant, scope, and audit isolation continues to hold.
52
+
53
+ ## Test
54
+
55
+ From a Stigmem repository checkout with development dependencies installed:
56
+
57
+ ```bash
58
+ uv run pytest node/tests/plugins/test_multi_tenant_plugin_scaffold.py \
59
+ node/tests/plugins/test_multi_tenant_plugin_validation.py
60
+ ```
61
+
62
+ The package itself ships no separate test tree; upstream plugin validation
63
+ lives in `node/tests/plugins/`.
64
+
65
+ ## Uninstall
66
+
67
+ ```bash
68
+ pip uninstall stigmem-plugin-multi-tenant
69
+ ```
70
+
71
+ Removing the package is sufficient. The gate environment variable becomes moot
72
+ once the entry point is no longer discoverable.
73
+
74
+ ## Project Links
75
+
76
+ - Repository: <https://github.com/eidetic-labs/stigmem>
77
+ - Feature record: <https://github.com/eidetic-labs/stigmem/tree/main/features/multi-tenant>
78
+ - Plugin source: <https://github.com/eidetic-labs/stigmem/tree/main/experimental/multi-tenant>
79
+ - Issue tracker: <https://github.com/eidetic-labs/stigmem/issues>
@@ -0,0 +1,19 @@
1
+ # Multi-Tenant Scoping Status
2
+
3
+ This file is a compatibility pointer for existing
4
+ `experimental/multi-tenant/` links.
5
+
6
+ The canonical ADR-020 status record now lives at
7
+ [`features/multi-tenant/status.md`](../../features/multi-tenant/status.md).
8
+
9
+ Current summary:
10
+
11
+ - Status: `active`
12
+ - Stability: `experimental`
13
+ - Default surface: `opt-in`
14
+ - Implementation path: `experimental/multi-tenant/`
15
+ - Package: `stigmem-plugin-multi-tenant`
16
+ - Canonical spec: none currently assigned
17
+
18
+ The implementation package remains here during transition. Product status,
19
+ gates, history, and release-facing facts belong in the feature record.
@@ -0,0 +1,14 @@
1
+ # Multi-Tenant Scoping Concept
2
+
3
+ This file is a compatibility pointer for existing
4
+ `experimental/multi-tenant/concept.md` links.
5
+
6
+ The canonical ADR-020 feature record now lives at
7
+ [`features/multi-tenant/`](../../features/multi-tenant/).
8
+
9
+ - Feature overview: [`features/multi-tenant/README.md`](../../features/multi-tenant/README.md)
10
+ - Behavior spec: [`features/multi-tenant/spec.md`](../../features/multi-tenant/spec.md)
11
+ - Evidence and coverage gaps: [`features/multi-tenant/evidence.md`](../../features/multi-tenant/evidence.md)
12
+ - Security analysis: [`features/multi-tenant/security.md`](../../features/multi-tenant/security.md)
13
+
14
+ Product concept detail belongs in the feature record.
@@ -0,0 +1,53 @@
1
+ [project]
2
+ name = "stigmem-plugin-multi-tenant"
3
+ version = "0.1.0"
4
+ description = "Experimental multi-tenant isolation plugin for Stigmem."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { text = "Apache-2.0" }
8
+ authors = [
9
+ { name = "Eidetic Labs", email = "oss@eidetic-labs.ai" },
10
+ ]
11
+ keywords = ["stigmem", "plugins", "multi-tenant", "tenant-isolation", "authorization"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: Apache Software License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
20
+ ]
21
+ dependencies = [
22
+ "pydantic>=2,<3",
23
+ "stigmem-node>=0.9.0a8,<1.0.0",
24
+ ]
25
+
26
+ [project.entry-points."stigmem.plugins"]
27
+ multi-tenant = "stigmem_plugin_multi_tenant:plugin_manifest"
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/eidetic-labs/stigmem"
31
+ Documentation = "https://github.com/eidetic-labs/stigmem/tree/main/features/multi-tenant"
32
+ Repository = "https://github.com/eidetic-labs/stigmem"
33
+ Issues = "https://github.com/eidetic-labs/stigmem/issues"
34
+
35
+ [build-system]
36
+ requires = ["hatchling"]
37
+ build-backend = "hatchling.build"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/stigmem_plugin_multi_tenant"]
41
+
42
+ [tool.hatch.build.targets.wheel.sources]
43
+ "src" = ""
44
+
45
+ [tool.hatch.build.targets.sdist]
46
+ include = [
47
+ "README.md",
48
+ "STATUS.md",
49
+ "security.md",
50
+ "spec.md",
51
+ "concept.md",
52
+ "src/stigmem_plugin_multi_tenant/**/*.py",
53
+ ]
@@ -0,0 +1,28 @@
1
+ ---
2
+ feature: multi-tenant
3
+ spec_id: Spec-X0-Multi-Tenant-Scoping
4
+ status: Experimental
5
+ applies_to: stigmem v0.9.0a8
6
+ last_updated: 2026-05-24
7
+ owned_risks: []
8
+ contributed_risks:
9
+ - R-01
10
+ - R-02
11
+ - R-21
12
+ ---
13
+
14
+ # Multi-Tenant Scoping Security
15
+
16
+ This file is a compatibility pointer for existing
17
+ `experimental/multi-tenant/` security links.
18
+
19
+ The canonical ADR-020 security record now lives at
20
+ [`features/multi-tenant/security.md`](../../features/multi-tenant/security.md).
21
+
22
+ Current risk summary:
23
+
24
+ - Owned risks: none.
25
+ - Contributed risks: R-01, R-02, and R-21.
26
+
27
+ Product security analysis, operator scenarios, conformance pointers, residual
28
+ risk, and advisory history belong in the feature record.
@@ -0,0 +1,122 @@
1
+ ---
2
+ spec_id: Spec-X4-Multi-Tenant-Scoping
3
+ version: 0.1.0-alpha.0
4
+ status: Experimental
5
+ applies_to: future experimental plugin line
6
+ last_updated: 2026-05-24
7
+ supersedes: features/multi-tenant/spec.md package projection
8
+ depends_on:
9
+ - Spec-01-Fact-Model >= 0.1.0-alpha.0
10
+ - Spec-02-Scopes-and-ACL >= 0.1.0-alpha.0
11
+ - Spec-03-HTTP-API >= 0.1.0-alpha.0
12
+ - Spec-06-Capability-Tokens >= 0.1.0-alpha.0
13
+ - Spec-07-Recall-Pipeline >= 0.1.0-alpha.0
14
+ - Spec-09-Audit-Log >= 0.1.0-alpha.0
15
+ title: Multi-Tenant Scoping
16
+ sidebar_label: Multi-Tenant Scoping
17
+ audience: Spec
18
+ description: "Package projection for multi-tenant scoping semantics."
19
+ stability: experimental
20
+ since: 0.9.0a8
21
+ ---
22
+
23
+ # Multi-Tenant Scoping Spec
24
+
25
+ ## Scope
26
+
27
+ Multi-tenant scoping defines how a single Stigmem node can isolate facts,
28
+ gardens, audit records, API keys, recall, and plugin hooks by `tenant_id` when
29
+ the opt-in `stigmem-plugin-multi-tenant` package is registered and explicitly
30
+ enabled.
31
+
32
+ This feature has no Spec-X assignment. If it is reintroduced into the supported
33
+ surface as protocol behavior, it must receive a numbered Spec-X per ADR-010 and
34
+ pass ADR-008 gates.
35
+
36
+ ## Default Behavior
37
+
38
+ Default installs resolve every caller into the single `default` tenant. This is
39
+ true even if the stored API key carries a non-default tenant ID.
40
+
41
+ Default behavior exists so the core node can keep storage compatibility columns
42
+ without making multi-tenancy part of the default supported surface.
43
+ Single-tenant deployments do not need the plugin.
44
+
45
+ ## Plugin-Enabled Behavior
46
+
47
+ When the plugin is registered and `STIGMEM_MULTI_TENANT_ENABLED=true`, the
48
+ `tenant_resolve` hook preserves the API key's tenant ID. The resolved tenant
49
+ then flows through tenant-scoped HTTP, storage, recall, audit, observability,
50
+ subscription, and plugin-hook paths that accept request tenant context.
51
+
52
+ Existing node-level federation replication remains constrained to the `default`
53
+ tenant in v0.9.0a8. Non-default tenant federation needs a dedicated design,
54
+ conformance vectors, and risk disposition before it can be promoted.
55
+
56
+ ```text
57
+ Bearer token -> Identity(entity_uri, permissions, tenant_id)
58
+ |
59
+ v
60
+ TenantContext(tenant_id)
61
+ |
62
+ v
63
+ storage and hook paths filter by tenant_id
64
+ ```
65
+
66
+ ## Isolation Guarantees
67
+
68
+ | Resource | Isolation mechanism |
69
+ | --- | --- |
70
+ | Facts | `tenant_id` column on `facts`; reads and writes filter by caller tenant. |
71
+ | Gardens | Garden slug lookup is scoped by tenant; the same slug can exist in multiple tenants. |
72
+ | Garden ACL | Membership checks resolve garden identity within the caller tenant. |
73
+ | Audit log | Audit rows carry `tenant_id`; audit queries are tenant-scoped. |
74
+ | API keys | Each key belongs to exactly one tenant. |
75
+ | Federation | Existing node-level pull exports only `default` tenant facts; tenant-aware non-default federation is deferred. |
76
+ | Plugin hooks | Hook payloads receive tenant context through the plugin framework. |
77
+
78
+ ## Non-Isolated Node Surfaces
79
+
80
+ The following surfaces are node-level, not tenant-level:
81
+
82
+ - environment settings;
83
+ - health and well-known endpoints;
84
+ - node identity and signing keys;
85
+ - process resources such as CPU, memory, disk, and network capacity.
86
+
87
+ Operators who host independent tenants on one node must pair tenant isolation
88
+ with transport authentication, per-tenant quotas, audit review, and operational
89
+ resource controls.
90
+
91
+ ## API Key Provisioning
92
+
93
+ Tenant ID is assigned when an API key is created. Keys created without an
94
+ explicit `tenant_id` use `default`.
95
+
96
+ ```json
97
+ {
98
+ "entity_uri": "service:payments",
99
+ "permissions": ["read", "write"],
100
+ "tenant_id": "acme-corp"
101
+ }
102
+ ```
103
+
104
+ `GET /v1/me` returns the resolved identity and tenant ID. With the plugin
105
+ disabled, non-default keys resolve to `default`; with the plugin enabled, the
106
+ stored non-default tenant is preserved.
107
+
108
+ ## Tenant Naming
109
+
110
+ `tenant_id` is an opaque string. Operators should use a stable URL-safe slug
111
+ such as `acme-corp`, `team-ml`, or `staging`. The node does not maintain a
112
+ separate tenant registry; tenant identity is implicit in key assignment.
113
+
114
+ ## Non-Goals
115
+
116
+ - This feature does not make multi-tenancy part of the default supported
117
+ surface.
118
+ - This feature does not publish a signed plugin artifact.
119
+ - This feature does not define per-tenant billing policy.
120
+ - This feature does not provide tenant-aware non-default federation.
121
+ - This feature does not provide process-level resource isolation between
122
+ tenants.
@@ -0,0 +1,12 @@
1
+ """Experimental multi-tenant plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .config import MultiTenantConfig
6
+ from .manifest import PLUGIN_NAME, plugin_manifest
7
+
8
+ __all__ = [
9
+ "PLUGIN_NAME",
10
+ "MultiTenantConfig",
11
+ "plugin_manifest",
12
+ ]
@@ -0,0 +1,28 @@
1
+ """Configuration schema for the multi-tenant plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Mapping
7
+
8
+ from pydantic import BaseModel
9
+
10
+
11
+ class MultiTenantConfig(BaseModel):
12
+ """Operator-controlled gate for non-default tenant resolution."""
13
+
14
+ enabled: bool = False
15
+
16
+
17
+ def load_config_from_env(environ: Mapping[str, str] | None = None) -> MultiTenantConfig:
18
+ """Load multi-tenant plugin gates from environment variables."""
19
+
20
+ env = environ if environ is not None else os.environ
21
+ return MultiTenantConfig(
22
+ enabled=_env_bool(env, "STIGMEM_MULTI_TENANT_ENABLED"),
23
+ )
24
+
25
+
26
+ def _env_bool(env: Mapping[str, str], name: str) -> bool:
27
+ raw = env.get(name, "")
28
+ return raw.strip().lower() in {"1", "true", "yes", "on"}
@@ -0,0 +1,127 @@
1
+ """Hook handlers for the multi-tenant plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, TypeVar
6
+
7
+ from pydantic import ValidationError
8
+ from stigmem_node.plugins import (
9
+ Allow,
10
+ Deny,
11
+ PluginContext,
12
+ PluginHealth,
13
+ PluginHealthStatus,
14
+ TenantContext,
15
+ )
16
+ from stigmem_node.tenant import TenantIdError, validate_tenant_id
17
+
18
+ from .config import load_config_from_env
19
+
20
+ T = TypeVar("T")
21
+
22
+
23
+ def config_validate(_ctx: PluginContext, **_: Any) -> Allow | Deny:
24
+ """Validate operator gates before the plugin is registered."""
25
+
26
+ try:
27
+ load_config_from_env()
28
+ except ValidationError as exc:
29
+ return Deny(f"invalid multi-tenant plugin config: {exc}")
30
+ return Allow()
31
+
32
+
33
+ def tenant_resolve(_ctx: PluginContext, value: TenantContext, **kwargs: Any) -> TenantContext:
34
+ """Resolve non-default tenants only when the plugin is explicitly enabled.
35
+
36
+ When ``STIGMEM_MULTI_TENANT_ENABLED`` is false, this hook returns the
37
+ inbound context unchanged. When enabled, it validates and normalizes the
38
+ candidate tenant ID before promoting the context to ``resolved``.
39
+ """
40
+
41
+ config = load_config_from_env()
42
+ if not config.enabled:
43
+ return value
44
+
45
+ identity = kwargs.get("identity")
46
+ tenant_id = getattr(identity, "tenant_id", None)
47
+ if not tenant_id:
48
+ tenant_id = value.metadata.get("source_tenant_id")
49
+ if not tenant_id:
50
+ return value
51
+ try:
52
+ normalized_tenant_id = validate_tenant_id(str(tenant_id))
53
+ except TenantIdError:
54
+ return value
55
+ return TenantContext(
56
+ tenant_id=normalized_tenant_id,
57
+ metadata={
58
+ **value.metadata,
59
+ "tenant_context_source": "resolved",
60
+ "resolved_by": "stigmem-plugin-multi-tenant",
61
+ },
62
+ )
63
+
64
+
65
+ def pre_assert_authorize(_ctx: PluginContext, **_: Any) -> Allow:
66
+ """Acknowledge core-enforced tenant write authorization.
67
+
68
+ Behavior is identical regardless of ``STIGMEM_MULTI_TENANT_ENABLED``:
69
+ this hook always returns ``Allow()`` because tenant isolation is enforced by
70
+ core write paths. The plugin contributes tenant resolution, not write
71
+ authorization.
72
+ """
73
+
74
+ return Allow()
75
+
76
+
77
+ def pre_recall_authorize(_ctx: PluginContext, **_: Any) -> Allow:
78
+ """Acknowledge core-enforced tenant recall authorization.
79
+
80
+ Behavior is identical regardless of ``STIGMEM_MULTI_TENANT_ENABLED``:
81
+ this hook always returns ``Allow()`` because tenant isolation is enforced by
82
+ core recall paths. The plugin contributes tenant resolution, not recall
83
+ authorization.
84
+ """
85
+
86
+ return Allow()
87
+
88
+
89
+ def recall_filter(_ctx: PluginContext, value: T, **_: Any) -> T:
90
+ """Return recall/query results unchanged after core tenant filtering.
91
+
92
+ With the gate disabled or enabled, this hook is a pass-through because core
93
+ recall/query SQL already filters by the resolved request tenant.
94
+ """
95
+
96
+ return value
97
+
98
+
99
+ def federation_outbound_filter(_ctx: PluginContext, value: T, **_: Any) -> T:
100
+ """Return outbound federation batches unchanged after core tenant filtering.
101
+
102
+ With the gate disabled or enabled, this hook is a pass-through because
103
+ v0.9.0a8 node-level federation is pinned to the default tenant by core.
104
+ """
105
+
106
+ return value
107
+
108
+
109
+ def migration_register(_ctx: PluginContext, value: T, **_: Any) -> T:
110
+ """Return core/plugin migrations unchanged.
111
+
112
+ With the gate disabled or enabled, this hook is a pass-through.
113
+ Tenant columns remain part of the core storage compatibility schema so
114
+ existing single-tenant data keeps the `default` partition. The plugin owns
115
+ non-default tenant resolution and validation, not the storage columns.
116
+ """
117
+
118
+ return value
119
+
120
+
121
+ def health_check(_ctx: PluginContext) -> PluginHealth:
122
+ """Report scaffold health for registry lifecycle tests."""
123
+
124
+ return PluginHealth(
125
+ status=PluginHealthStatus.HEALTHY,
126
+ message="multi-tenant plugin scaffold registered",
127
+ )
@@ -0,0 +1,48 @@
1
+ """Plugin manifest factory for multi-tenant isolation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from stigmem_node.plugins import PluginManifest
6
+
7
+ from . import handlers
8
+ from .config import MultiTenantConfig
9
+
10
+ PLUGIN_NAME = "stigmem-plugin-multi-tenant"
11
+ PLUGIN_VERSION = "0.1.0"
12
+ REQUIRES_STIGMEM = ">=0.9.0a3"
13
+
14
+
15
+ def plugin_manifest() -> PluginManifest:
16
+ """Return the entry-point manifest consumed by ``stigmem.plugins`` discovery."""
17
+
18
+ return PluginManifest(
19
+ name=PLUGIN_NAME,
20
+ version=PLUGIN_VERSION,
21
+ requires_stigmem=REQUIRES_STIGMEM,
22
+ capabilities=frozenset(
23
+ {
24
+ "facts.read",
25
+ "facts.write",
26
+ "recall.read",
27
+ "federation.read",
28
+ "federation.write",
29
+ "identity.read",
30
+ "tenant.read",
31
+ "tenant.write",
32
+ "audit.emit",
33
+ "config.read",
34
+ }
35
+ ),
36
+ hooks={
37
+ "config_validate": handlers.config_validate,
38
+ "tenant_resolve": handlers.tenant_resolve,
39
+ "pre_assert_authorize": handlers.pre_assert_authorize,
40
+ "pre_recall_authorize": handlers.pre_recall_authorize,
41
+ "recall_filter": handlers.recall_filter,
42
+ "federation_outbound_filter": handlers.federation_outbound_filter,
43
+ "migration_register": handlers.migration_register,
44
+ },
45
+ config_schema=MultiTenantConfig,
46
+ health_check=handlers.health_check,
47
+ async_safe=True,
48
+ )