anna-app-core 0.2.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.
- anna_app_core-0.2.0/.gitignore +170 -0
- anna_app_core-0.2.0/PKG-INFO +42 -0
- anna_app_core-0.2.0/README.md +30 -0
- anna_app_core-0.2.0/pyproject.toml +25 -0
- anna_app_core-0.2.0/src/anna_app_core/__init__.py +60 -0
- anna_app_core-0.2.0/src/anna_app_core/acl.py +151 -0
- anna_app_core-0.2.0/src/anna_app_core/dispatcher.py +732 -0
- anna_app_core-0.2.0/src/anna_app_core/errors.py +33 -0
- anna_app_core-0.2.0/src/anna_app_core/manifest.py +209 -0
- anna_app_core-0.2.0/src/anna_app_core/protocols.py +130 -0
- anna_app_core-0.2.0/src/anna_app_core/runtime.py +73 -0
- anna_app_core-0.2.0/src/anna_app_core/versions.py +9 -0
- anna_app_core-0.2.0/tests/test_dispatcher_llm_agent.py +371 -0
- anna_app_core-0.2.0/tests/test_smoke.py +366 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# matrix agent repository
|
|
2
|
+
matrix/
|
|
3
|
+
|
|
4
|
+
# git diff files
|
|
5
|
+
git.diff
|
|
6
|
+
|
|
7
|
+
# Byte-compiled / optimized / DLL files
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
*$py.class
|
|
11
|
+
|
|
12
|
+
# C extensions
|
|
13
|
+
*.so
|
|
14
|
+
|
|
15
|
+
# Distribution / packaging
|
|
16
|
+
.Python
|
|
17
|
+
build/
|
|
18
|
+
develop-eggs/
|
|
19
|
+
dist/
|
|
20
|
+
# @anna-ai/app-runtime ships a hand-curated dist/ as the publishable
|
|
21
|
+
# artifact (it is not a build output) — keep it tracked.
|
|
22
|
+
!packages/anna-app-runtime/dist/
|
|
23
|
+
downloads/
|
|
24
|
+
eggs/
|
|
25
|
+
.eggs/
|
|
26
|
+
lib/
|
|
27
|
+
lib64/
|
|
28
|
+
parts/
|
|
29
|
+
sdist/
|
|
30
|
+
var/
|
|
31
|
+
wheels/
|
|
32
|
+
share/python-wheels/
|
|
33
|
+
*.egg-info/
|
|
34
|
+
.installed.cfg
|
|
35
|
+
*.egg
|
|
36
|
+
# setuptools sdist MANIFEST (only at repo root — must NOT swallow the
|
|
37
|
+
# packages/anna-app-schema/manifest/ schema bundle directory).
|
|
38
|
+
/MANIFEST
|
|
39
|
+
|
|
40
|
+
# PyInstaller
|
|
41
|
+
# Anchored to repo root for the same reason — `*.manifest` is too broad
|
|
42
|
+
# and was matching schema bundle files. Re-add specific paths if a real
|
|
43
|
+
# PyInstaller .manifest ever ships.
|
|
44
|
+
/*.manifest
|
|
45
|
+
*.spec
|
|
46
|
+
|
|
47
|
+
# Installer logs
|
|
48
|
+
pip-log.txt
|
|
49
|
+
pip-delete-this-directory.txt
|
|
50
|
+
|
|
51
|
+
# Unit test / coverage reports
|
|
52
|
+
htmlcov/
|
|
53
|
+
.tox/
|
|
54
|
+
.nox/
|
|
55
|
+
.coverage
|
|
56
|
+
.coverage.*
|
|
57
|
+
.cache
|
|
58
|
+
nosetests.xml
|
|
59
|
+
coverage.xml
|
|
60
|
+
*.cover
|
|
61
|
+
*.py,cover
|
|
62
|
+
.hypothesis/
|
|
63
|
+
.pytest_cache/
|
|
64
|
+
cover/
|
|
65
|
+
|
|
66
|
+
# Translations
|
|
67
|
+
*.mo
|
|
68
|
+
*.pot
|
|
69
|
+
|
|
70
|
+
# Django stuff:
|
|
71
|
+
*.log
|
|
72
|
+
local_settings.py
|
|
73
|
+
db.sqlite3
|
|
74
|
+
db.sqlite3-journal
|
|
75
|
+
|
|
76
|
+
# Flask stuff:
|
|
77
|
+
instance/
|
|
78
|
+
.webassets-cache
|
|
79
|
+
|
|
80
|
+
# Scrapy stuff:
|
|
81
|
+
.scrapy
|
|
82
|
+
|
|
83
|
+
# Sphinx documentation
|
|
84
|
+
docs/_build/
|
|
85
|
+
|
|
86
|
+
# PyBuilder
|
|
87
|
+
.pybuilder/
|
|
88
|
+
target/
|
|
89
|
+
|
|
90
|
+
# Jupyter Notebook
|
|
91
|
+
.ipynb_checkpoints
|
|
92
|
+
|
|
93
|
+
# IPython
|
|
94
|
+
profile_default/
|
|
95
|
+
ipython_config.py
|
|
96
|
+
|
|
97
|
+
# pyenv
|
|
98
|
+
.python-version
|
|
99
|
+
|
|
100
|
+
# pipenv
|
|
101
|
+
Pipfile.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
poetry.lock
|
|
105
|
+
|
|
106
|
+
# pdm
|
|
107
|
+
.pdm.toml
|
|
108
|
+
|
|
109
|
+
# PEP 582
|
|
110
|
+
__pypackages__/
|
|
111
|
+
|
|
112
|
+
# Celery stuff
|
|
113
|
+
celerybeat-schedule
|
|
114
|
+
celerybeat.pid
|
|
115
|
+
|
|
116
|
+
# SageMath parsed files
|
|
117
|
+
*.sage.py
|
|
118
|
+
|
|
119
|
+
# Environments
|
|
120
|
+
.env
|
|
121
|
+
.venv
|
|
122
|
+
env/
|
|
123
|
+
venv/
|
|
124
|
+
ENV/
|
|
125
|
+
env.bak/
|
|
126
|
+
venv.bak/
|
|
127
|
+
|
|
128
|
+
# Spyder project settings
|
|
129
|
+
.spyderproject
|
|
130
|
+
.spyproject
|
|
131
|
+
|
|
132
|
+
# Rope project settings
|
|
133
|
+
.ropeproject
|
|
134
|
+
|
|
135
|
+
# mkdocs documentation
|
|
136
|
+
/site
|
|
137
|
+
|
|
138
|
+
# mypy
|
|
139
|
+
.mypy_cache/
|
|
140
|
+
.dmypy.json
|
|
141
|
+
dmypy.json
|
|
142
|
+
|
|
143
|
+
# Pyre type checker
|
|
144
|
+
.pyre/
|
|
145
|
+
|
|
146
|
+
# pytype static type analyzer
|
|
147
|
+
.pytype/
|
|
148
|
+
|
|
149
|
+
# Cython debug symbols
|
|
150
|
+
cython_debug/
|
|
151
|
+
|
|
152
|
+
# IDE
|
|
153
|
+
.idea/
|
|
154
|
+
*.swp
|
|
155
|
+
*.swo
|
|
156
|
+
*~
|
|
157
|
+
.DS_Store
|
|
158
|
+
|
|
159
|
+
# Project specific
|
|
160
|
+
*.tmp
|
|
161
|
+
*.bak
|
|
162
|
+
|
|
163
|
+
# Security - RSA keys
|
|
164
|
+
private_key.pem
|
|
165
|
+
*.pem
|
|
166
|
+
!public_key.pem
|
|
167
|
+
|
|
168
|
+
scripts/nats_auth
|
|
169
|
+
# Anna App runtime cache
|
|
170
|
+
static/anna-apps-cache/
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: anna-app-core
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Anna App platform core: manifest schemas, RPC dispatcher, protocols, errors. Shared by matrix-nexus and the local-dev runtime.
|
|
5
|
+
Project-URL: Documentation, https://github.com/talentai/matrix-nexus/blob/main/docs/design/anna-app-local-dev-and-test.md
|
|
6
|
+
Author: Talent AI
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: anna,anna-app,executa
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# anna-app-core
|
|
14
|
+
|
|
15
|
+
Shared protocol/error/ACL primitives **and** the Anna App RPC dispatcher,
|
|
16
|
+
manifest schemas, and runtime helpers used by the [Anna App](https://github.com/talentai/matrix-nexus/blob/main/docs/design/anna-app-local-dev-and-test.md)
|
|
17
|
+
platform.
|
|
18
|
+
|
|
19
|
+
This package is the **stable contract surface** that both `matrix-nexus`
|
|
20
|
+
(production host) and `anna-app-runtime-local` (local dev harness) depend on,
|
|
21
|
+
so neither has to pull in the other's runtime.
|
|
22
|
+
|
|
23
|
+
## What's here (v0.2.x)
|
|
24
|
+
|
|
25
|
+
- **Manifest (Pydantic v2)** — `AppManifest`, `UiManifestSection`,
|
|
26
|
+
`UiBundleSection`, `UiViewSpec`, `UiHostApiSpec`, `UiSize`, `WindowViewMeta`,
|
|
27
|
+
`AppDevConfig`, `ManifestExecutaRef`
|
|
28
|
+
- **Dispatcher** — `dispatch(method, params, *, manifest, store, ...)` (the
|
|
29
|
+
full JSON-RPC entry point; previously lived in `matrix-nexus`)
|
|
30
|
+
- **Protocols** — `WindowStoreProtocol` (minimum surface RPC handlers expect
|
|
31
|
+
from a window store)
|
|
32
|
+
- **Errors** — `HostRpcError`, `WindowError`, `WindowPermissionError`
|
|
33
|
+
- **ACL** — `host_api_allows(manifest_dict, ns, method) -> bool`
|
|
34
|
+
- **Runtime helpers** — `select_view`, `scopes_from_manifest`, `view_meta`
|
|
35
|
+
- **Versions** — `DISPATCHER_VERSION`, `SDK_VERSION`, `__version__`
|
|
36
|
+
|
|
37
|
+
## Versioning
|
|
38
|
+
|
|
39
|
+
`0.2.0a2` — docs catch-up over `0.2.0a1` (no code changes); the dispatcher
|
|
40
|
+
+ Pydantic manifest extraction landed in `0.2.0a1`. Pin policy lives in the
|
|
41
|
+
central [VERSIONS.md](../VERSIONS.md) and the operator runbook at
|
|
42
|
+
[`docs/operations/anna-app-release-flow.md`](https://github.com/talentai/matrix-nexus/blob/main/docs/operations/anna-app-release-flow.md).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# anna-app-core
|
|
2
|
+
|
|
3
|
+
Shared protocol/error/ACL primitives **and** the Anna App RPC dispatcher,
|
|
4
|
+
manifest schemas, and runtime helpers used by the [Anna App](https://github.com/talentai/matrix-nexus/blob/main/docs/design/anna-app-local-dev-and-test.md)
|
|
5
|
+
platform.
|
|
6
|
+
|
|
7
|
+
This package is the **stable contract surface** that both `matrix-nexus`
|
|
8
|
+
(production host) and `anna-app-runtime-local` (local dev harness) depend on,
|
|
9
|
+
so neither has to pull in the other's runtime.
|
|
10
|
+
|
|
11
|
+
## What's here (v0.2.x)
|
|
12
|
+
|
|
13
|
+
- **Manifest (Pydantic v2)** — `AppManifest`, `UiManifestSection`,
|
|
14
|
+
`UiBundleSection`, `UiViewSpec`, `UiHostApiSpec`, `UiSize`, `WindowViewMeta`,
|
|
15
|
+
`AppDevConfig`, `ManifestExecutaRef`
|
|
16
|
+
- **Dispatcher** — `dispatch(method, params, *, manifest, store, ...)` (the
|
|
17
|
+
full JSON-RPC entry point; previously lived in `matrix-nexus`)
|
|
18
|
+
- **Protocols** — `WindowStoreProtocol` (minimum surface RPC handlers expect
|
|
19
|
+
from a window store)
|
|
20
|
+
- **Errors** — `HostRpcError`, `WindowError`, `WindowPermissionError`
|
|
21
|
+
- **ACL** — `host_api_allows(manifest_dict, ns, method) -> bool`
|
|
22
|
+
- **Runtime helpers** — `select_view`, `scopes_from_manifest`, `view_meta`
|
|
23
|
+
- **Versions** — `DISPATCHER_VERSION`, `SDK_VERSION`, `__version__`
|
|
24
|
+
|
|
25
|
+
## Versioning
|
|
26
|
+
|
|
27
|
+
`0.2.0a2` — docs catch-up over `0.2.0a1` (no code changes); the dispatcher
|
|
28
|
+
+ Pydantic manifest extraction landed in `0.2.0a1`. Pin policy lives in the
|
|
29
|
+
central [VERSIONS.md](../VERSIONS.md) and the operator runbook at
|
|
30
|
+
[`docs/operations/anna-app-release-flow.md`](https://github.com/talentai/matrix-nexus/blob/main/docs/operations/anna-app-release-flow.md).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "anna-app-core"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Anna App platform core: manifest schemas, RPC dispatcher, protocols, errors. Shared by matrix-nexus and the local-dev runtime."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Talent AI" }]
|
|
13
|
+
keywords = ["anna", "anna-app", "executa"]
|
|
14
|
+
|
|
15
|
+
# pydantic v2 is required for the AppManifest schema; everything else is
|
|
16
|
+
# pure stdlib.
|
|
17
|
+
dependencies = [
|
|
18
|
+
"pydantic>=2.0,<3.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Documentation = "https://github.com/talentai/matrix-nexus/blob/main/docs/design/anna-app-local-dev-and-test.md"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/anna_app_core"]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""anna-app-core: stable contract surface shared by matrix-nexus and
|
|
2
|
+
anna-app-runtime-local.
|
|
3
|
+
|
|
4
|
+
v0.2.0a1 — full extraction. The complete RPC dispatcher, manifest pydantic
|
|
5
|
+
schemas, and helper functions now live here; matrix-nexus re-exports them
|
|
6
|
+
for backward compatibility (see README.md).
|
|
7
|
+
|
|
8
|
+
v0.2.0a2 — docs catch-up only (README rewrite). No API changes.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .acl import host_api_allows
|
|
12
|
+
from .dispatcher import dispatch
|
|
13
|
+
from .errors import HostRpcError, WindowError, WindowPermissionError
|
|
14
|
+
from .manifest import (
|
|
15
|
+
AppDevConfig,
|
|
16
|
+
AppManifest,
|
|
17
|
+
ManifestExecutaRef,
|
|
18
|
+
UiBundleSection,
|
|
19
|
+
UiHostApiSpec,
|
|
20
|
+
UiManifestSection,
|
|
21
|
+
UiSize,
|
|
22
|
+
UiViewSpec,
|
|
23
|
+
WindowViewMeta,
|
|
24
|
+
)
|
|
25
|
+
from .protocols import WindowStoreProtocol
|
|
26
|
+
from .runtime import scopes_from_manifest, select_view, view_meta
|
|
27
|
+
from .versions import DISPATCHER_VERSION, SDK_VERSION
|
|
28
|
+
|
|
29
|
+
__version__ = "0.2.0"
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
# errors
|
|
33
|
+
"HostRpcError",
|
|
34
|
+
"WindowError",
|
|
35
|
+
"WindowPermissionError",
|
|
36
|
+
# protocols
|
|
37
|
+
"WindowStoreProtocol",
|
|
38
|
+
# acl
|
|
39
|
+
"host_api_allows",
|
|
40
|
+
# dispatcher
|
|
41
|
+
"dispatch",
|
|
42
|
+
# manifest schemas
|
|
43
|
+
"AppManifest",
|
|
44
|
+
"AppDevConfig",
|
|
45
|
+
"ManifestExecutaRef",
|
|
46
|
+
"UiBundleSection",
|
|
47
|
+
"UiHostApiSpec",
|
|
48
|
+
"UiManifestSection",
|
|
49
|
+
"UiSize",
|
|
50
|
+
"UiViewSpec",
|
|
51
|
+
"WindowViewMeta",
|
|
52
|
+
# runtime helpers
|
|
53
|
+
"select_view",
|
|
54
|
+
"scopes_from_manifest",
|
|
55
|
+
"view_meta",
|
|
56
|
+
# versions
|
|
57
|
+
"DISPATCHER_VERSION",
|
|
58
|
+
"SDK_VERSION",
|
|
59
|
+
"__version__",
|
|
60
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Host-API ACL check.
|
|
2
|
+
|
|
3
|
+
Accepts either a typed ``AppManifest`` (preferred — production + harness)
|
|
4
|
+
or a plain ``dict`` (e.g. JSON-loaded manifest before pydantic validation).
|
|
5
|
+
|
|
6
|
+
Mirrors the pre-extraction implementation in
|
|
7
|
+
``matrix-nexus/src/services/anna_app_runtime_service.py::host_api_allows``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, Union
|
|
13
|
+
|
|
14
|
+
from .manifest import AppManifest
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _ui_host_api(
|
|
18
|
+
manifest: Union[AppManifest, dict[str, Any]],
|
|
19
|
+
) -> dict[str, Any] | None:
|
|
20
|
+
"""Return the ``ui.host_api`` mapping (ns -> list[method] | dict) or
|
|
21
|
+
``None`` if the manifest has no UI section.
|
|
22
|
+
|
|
23
|
+
Note: most namespaces resolve to ``list[str]`` of allowed methods, but
|
|
24
|
+
``agent`` is a structured dict (see ``AgentHostApiSpec``).
|
|
25
|
+
"""
|
|
26
|
+
if isinstance(manifest, AppManifest):
|
|
27
|
+
if manifest.ui is None:
|
|
28
|
+
return None
|
|
29
|
+
api = manifest.ui.host_api
|
|
30
|
+
return {
|
|
31
|
+
"tools": api.tools,
|
|
32
|
+
"chat": api.chat,
|
|
33
|
+
"artifact": api.artifact,
|
|
34
|
+
"llm": api.llm,
|
|
35
|
+
"agent": api.agent.model_dump() if api.agent is not None else None,
|
|
36
|
+
"fs": api.fs,
|
|
37
|
+
"storage": api.storage,
|
|
38
|
+
"prefs": api.prefs,
|
|
39
|
+
"files": api.files,
|
|
40
|
+
"user_files": api.user_files,
|
|
41
|
+
}
|
|
42
|
+
ui = manifest.get("ui") if isinstance(manifest, dict) else None
|
|
43
|
+
if ui is None:
|
|
44
|
+
return None
|
|
45
|
+
return ui.get("host_api") or {}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def host_api_allows(
|
|
49
|
+
manifest: Union[AppManifest, dict[str, Any]],
|
|
50
|
+
ns: str,
|
|
51
|
+
method: str,
|
|
52
|
+
) -> bool:
|
|
53
|
+
"""Return ``True`` iff ``ns.method`` is granted by the manifest's ACL.
|
|
54
|
+
|
|
55
|
+
`window.*` is always granted. ``tools.invoke`` and ``tools.list`` are
|
|
56
|
+
granted whenever the ``tools`` set is non-empty (per-tool gating happens
|
|
57
|
+
inside the handler). ``agent.session.*`` is granted when at least one
|
|
58
|
+
submode (``auto`` / ``fixed``) is enabled — the handler validates the
|
|
59
|
+
selected submode against the structured spec.
|
|
60
|
+
"""
|
|
61
|
+
if ns == "window":
|
|
62
|
+
return True
|
|
63
|
+
api_map = _ui_host_api(manifest)
|
|
64
|
+
if api_map is None:
|
|
65
|
+
return False
|
|
66
|
+
methods = api_map.get(ns)
|
|
67
|
+
if not methods:
|
|
68
|
+
return False
|
|
69
|
+
if ns == "agent":
|
|
70
|
+
# Structured form: ``{session: {auto: bool, fixed: {client_ids:[]}}, tools: [...]}``.
|
|
71
|
+
if not method.startswith("session."):
|
|
72
|
+
return False
|
|
73
|
+
sess = methods.get("session") if isinstance(methods, dict) else None
|
|
74
|
+
if not isinstance(sess, dict):
|
|
75
|
+
return False
|
|
76
|
+
auto_on = bool(sess.get("auto"))
|
|
77
|
+
fixed_on = bool(sess.get("fixed"))
|
|
78
|
+
return auto_on or fixed_on
|
|
79
|
+
if isinstance(methods, list):
|
|
80
|
+
if "*" in methods:
|
|
81
|
+
return True
|
|
82
|
+
if method in methods:
|
|
83
|
+
return True
|
|
84
|
+
if ns == "tools" and method in ("invoke", "list"):
|
|
85
|
+
return True
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# -----------------------------------------------------------------------------
|
|
90
|
+
# Scope capability — manifest.host_capabilities
|
|
91
|
+
# -----------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_ACTIONS = ("read", "write")
|
|
95
|
+
_SCOPES = ("user", "app", "tool")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _host_caps_list(
|
|
99
|
+
manifest: Union[AppManifest, dict[str, Any]],
|
|
100
|
+
) -> list[str]:
|
|
101
|
+
"""Normalise ``host_capabilities`` to a flat list[str]. Tolerates dict form."""
|
|
102
|
+
if isinstance(manifest, AppManifest):
|
|
103
|
+
raw: Any = list(manifest.host_capabilities or [])
|
|
104
|
+
else:
|
|
105
|
+
raw = manifest.get("host_capabilities") if isinstance(manifest, dict) else None
|
|
106
|
+
raw = raw or []
|
|
107
|
+
if isinstance(raw, dict):
|
|
108
|
+
return [str(k) for k, v in raw.items() if v]
|
|
109
|
+
if isinstance(raw, (list, tuple)):
|
|
110
|
+
return [str(c) for c in raw]
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def host_capability_allows_scope(
|
|
115
|
+
manifest: Union[AppManifest, dict[str, Any]],
|
|
116
|
+
scope: str,
|
|
117
|
+
action: str,
|
|
118
|
+
*,
|
|
119
|
+
cross_owner: bool = False,
|
|
120
|
+
) -> bool:
|
|
121
|
+
"""Return ``True`` iff the manifest grants APS access to ``(scope, action)``.
|
|
122
|
+
|
|
123
|
+
``scope`` is one of ``user`` / ``app`` / ``tool``; ``action`` is ``read`` or
|
|
124
|
+
``write``. ``cross_owner`` is ``True`` when the call targets a different
|
|
125
|
+
``owner_id`` than the calling iframe (e.g. another app's ``scope=app``
|
|
126
|
+
bucket). Self-owned ``scope=app`` access stays granted by the legacy
|
|
127
|
+
``aps.kv`` capability.
|
|
128
|
+
|
|
129
|
+
Capability strings recognised:
|
|
130
|
+
|
|
131
|
+
* ``aps.scope.admin`` → grants everything.
|
|
132
|
+
* ``aps.scope.<scope>.<action>`` → exact match.
|
|
133
|
+
* ``aps.kv`` → ``scope=app`` self-owned only.
|
|
134
|
+
"""
|
|
135
|
+
if scope not in _SCOPES:
|
|
136
|
+
return False
|
|
137
|
+
if action not in _ACTIONS:
|
|
138
|
+
return False
|
|
139
|
+
caps = set(_host_caps_list(manifest))
|
|
140
|
+
if "aps.scope.admin" in caps:
|
|
141
|
+
return True
|
|
142
|
+
fine = f"aps.scope.{scope}.{action}"
|
|
143
|
+
if fine in caps:
|
|
144
|
+
return True
|
|
145
|
+
# Legacy: ``aps.kv`` covers self-owned scope=app reads & writes.
|
|
146
|
+
if scope == "app" and not cross_owner and ("aps.kv" in caps or "aps" in caps):
|
|
147
|
+
return True
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
__all__ = ["host_api_allows", "host_capability_allows_scope"]
|