toolframe-core 0.6.2__py3-none-any.whl

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 (46) hide show
  1. toolframe_core/__init__.py +3 -0
  2. toolframe_core/_state_postgres.py +420 -0
  3. toolframe_core/_state_sqlite.py +339 -0
  4. toolframe_core/access.py +513 -0
  5. toolframe_core/api.py +700 -0
  6. toolframe_core/bundled_skills/toolframe-plugin-design/LICENSE +21 -0
  7. toolframe_core/bundled_skills/toolframe-plugin-design/SKILL.md +59 -0
  8. toolframe_core/bundled_skills/toolframe-plugin-design/agents/openai.yaml +4 -0
  9. toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/LICENSE +21 -0
  10. toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/pyproject.toml +21 -0
  11. toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/__init__.py +1 -0
  12. toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/docs/usage.md +13 -0
  13. toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/migrations/0001_init.sql +5 -0
  14. toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/page.py +25 -0
  15. toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/plugin.py +119 -0
  16. toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/src/toolframe_sample_notes/service.py +48 -0
  17. toolframe_core/bundled_skills/toolframe-plugin-design/references/design.md +60 -0
  18. toolframe_core/bundled_skills/toolframe-plugin-design/references/operations.md +133 -0
  19. toolframe_core/bundled_skills/toolframe-plugin-design/references/plugin-api.md +186 -0
  20. toolframe_core/cli.py +681 -0
  21. toolframe_core/config.py +267 -0
  22. toolframe_core/diagnostics.py +200 -0
  23. toolframe_core/graph.py +207 -0
  24. toolframe_core/host.py +2610 -0
  25. toolframe_core/maintenance.py +868 -0
  26. toolframe_core/mcp_access.py +259 -0
  27. toolframe_core/network.py +97 -0
  28. toolframe_core/oidc_access.py +187 -0
  29. toolframe_core/packages.py +728 -0
  30. toolframe_core/process.py +212 -0
  31. toolframe_core/redaction.py +120 -0
  32. toolframe_core/scaffold.py +142 -0
  33. toolframe_core/skills.py +210 -0
  34. toolframe_core/state.py +226 -0
  35. toolframe_core/state_admin.py +270 -0
  36. toolframe_core/stores.py +88 -0
  37. toolframe_core/templates.json +6 -0
  38. toolframe_core/templates.py +56 -0
  39. toolframe_core-0.6.2.dist-info/METADATA +465 -0
  40. toolframe_core-0.6.2.dist-info/RECORD +46 -0
  41. toolframe_core-0.6.2.dist-info/WHEEL +4 -0
  42. toolframe_core-0.6.2.dist-info/entry_points.txt +2 -0
  43. toolframe_core-0.6.2.dist-info/licenses/LICENSE +202 -0
  44. toolframe_core-0.6.2.dist-info/licenses/NOTICE +14 -0
  45. toolframe_core-0.6.2.dist-info/licenses/src/toolframe_core/bundled_skills/toolframe-plugin-design/LICENSE +21 -0
  46. toolframe_core-0.6.2.dist-info/licenses/src/toolframe_core/bundled_skills/toolframe-plugin-design/assets/sample-plugin/LICENSE +21 -0
@@ -0,0 +1,186 @@
1
+ # Public plugin contract
2
+
3
+ Target: `toolframe-sdk==0.3.0`, Core 0.6.1, Python 3.14. Use this packaged reference
4
+ and the starter when the source workspace is unavailable. If the installed SDK
5
+ version differs, inspect its public definitions before adapting these examples.
6
+
7
+ ## Package and service
8
+
9
+ One wheel provides one plugin, with one instance per application. A business
10
+ plugin depends on `toolframe-sdk`, not core or provider implementation modules.
11
+ The entry-point group is `toolframe.plugins`; its name and `PluginDefinition.id`
12
+ should be the same stable, lowercase hyphenated ID.
13
+
14
+ ```toml
15
+ [project.entry-points."toolframe.plugins"]
16
+ sample-notes = "toolframe_sample_notes.plugin:plugin"
17
+ ```
18
+
19
+ `plugin` is a `PluginDefinition`, not a factory function. It declares:
20
+
21
+ - `factory="package.module:Class"`: a subclass of `toolframe_sdk.Plugin`.
22
+ - `services={name: ServiceSpec(version=1, methods={...})}`: named business APIs.
23
+ - `MethodSpec(audience="business", required_scopes=("sample-notes.read",), input_schema=..., output_schema=..., read_only=...)`: JSON schemas
24
+ for each method. Use bounded inputs and explicit object properties. JSON Schema
25
+ defaults are documentation; implement actual Python defaults too.
26
+ - `requires={alias: Requirement(service=name, version=1, optional=False)}`:
27
+ consumed services. In the worker, call
28
+ `await self.context.call(alias, method, params)`.
29
+ - `config_schema`: accepted configuration; read values from `self.context.config`.
30
+
31
+ The host calls `await plugin.invoke(service, method, params)`. Dispatch only
32
+ declared methods and return JSON values matching the output schema. Signal
33
+ business failures with `PluginError(code, message, details)`. Use stable error
34
+ codes that pages and tools can interpret; avoid leaking credentials in details.
35
+
36
+ Keep module import and definitions free of side effects. `activate()` prepares
37
+ runtime resources, `health()` reports readiness, and `dispose()` releases owned
38
+ resources and awaits `super().dispose()`. Use `self.context.task(factory)` for
39
+ owned asynchronous work; account for cancellation. Each plugin runs in a separate
40
+ process. Cross-plugin Python objects and imports are not the service boundary.
41
+
42
+ ## Authorization and publication
43
+
44
+ Every definition requires `authorization=AuthorizationSpec(scopes={...})`, even
45
+ infrastructure plugins with an empty catalog. Legacy definitions fail validation;
46
+ no whole-plugin fallback permission is invented. Each scope ID starts with the
47
+ owning plugin ID, for example `sample-notes.read` and `sample-notes.write`:
48
+
49
+ ```python
50
+ authorization = AuthorizationSpec(
51
+ scopes={
52
+ "sample-notes.read": ScopeSpec(title="Read notes"),
53
+ "sample-notes.write": ScopeSpec(title="Capture notes"),
54
+ }
55
+ )
56
+ ```
57
+
58
+ Business methods declare `audience="business"` and `required_scopes`. Internal
59
+ dependency services declare `audience="internal"` and have no visitor scopes;
60
+ they cannot be invoked directly by Dashboard/API clients. Managed SQL uses this
61
+ internal audience and the caller plugin's storage ownership. Do not require the
62
+ visitor to hold storage administration permission merely to submit a form.
63
+
64
+ Dashboard pages declare `Resource.required_scopes`. `context.can("sample-notes.write")`
65
+ can disable editing controls. The host-issued context is supplied by the provider;
66
+ page code does not construct principals or handle browser passwords. Nested
67
+ service calls keep the admitted caller and check the callee's declared scopes.
68
+
69
+ `read_only=True` describes mutation behavior; it does not make a method public.
70
+ Use `public_api=True` only for deliberately public business methods, served at
71
+ `POST /api/PLUGIN/SERVICE/METHOD`. This HTTP route and llms documents are public
72
+ independently of Dashboard passwords. Leave sensitive methods `public_api=False`
73
+ and keep private content out of published documents.
74
+
75
+ | Entrance | Required decision |
76
+ | --- | --- |
77
+ | Dashboard and `/_toolframe/v1/services/...` | Set `access.mode="protected"` and `access.failure_mode="closed"` for confidential data; enforce method scopes. |
78
+ | `/api/...` | Only explicitly public business methods are reachable; Dashboard/MCP credentials do not protect them. |
79
+ | `/mcp` | Independent `mcp_access` policy; public by default, or a healthy MCP auth provider and scoped MCP credentials in protected mode. |
80
+ | llms documents | Public; publish only intentionally shareable content. |
81
+ | Local control / management | Direct loopback control and the management token are operator recovery paths; remote system calls require system authority. |
82
+
83
+ An MCP tool resource publishes a business method to the MCP catalog, whose policy
84
+ is independent of Dashboard login. For confidential tools, install/enable a
85
+ compatible `toolframe-mcp-auth` provider and set:
86
+
87
+ ```toml
88
+ [access]
89
+ mode = "protected"
90
+ failure_mode = "closed"
91
+
92
+ [mcp_access]
93
+ mode = "protected"
94
+ auth_provider = "mcp-auth"
95
+ ```
96
+
97
+ Use business-scoped MCP keys or configured delegated user grants; Dashboard
98
+ passwords and management tokens are not MCP keys. Protected MCP stays unavailable
99
+ if its verifier is absent or fails, independently of Dashboard fallback. New Core
100
+ applications write `failure_mode="closed"`; older files omitting it retain legacy
101
+ public fallback. Review existing choices before changing them. The access-password
102
+ page shows entrance states; its failure-policy editor requires Core 0.6.1.
103
+
104
+ Scopes authorize methods, not individual rows. Password labels identify grants,
105
+ not people. Per-person or tenant data needs a trusted identity and explicit
106
+ business-layer row filtering. Never use a caller-supplied label as identity.
107
+ System/internal methods cannot be published as public APIs or MCP tools. The
108
+ starter deliberately publishes a public read-only query; remove that flag when
109
+ adapting it for private data.
110
+
111
+ Page `service.call(..., activity=False)` is for background polling, so it does not
112
+ extend a session's idle deadline or last-use timestamp. Interactive calls default
113
+ to activity. Every new call rechecks current access; already admitted call trees
114
+ retain their snapshot if a password is subsequently revoked. Plugin workers are
115
+ trusted application code, not a sandbox for untrusted extensions.
116
+
117
+ ## Managed SQLite
118
+
119
+ Declare `requires={"storage": Requirement(service="toolframe.storage.sql")}`
120
+ and `store=StoreSchema(revision="v1", migrations=(Migration(...),))` when needed.
121
+ The provider allocates a database for the stable plugin ID. It retains data when
122
+ a plugin is disabled, removed or reloaded. Core's own SQLite holds management
123
+ state and is not business storage.
124
+
125
+ ```python
126
+ storage = self.context.storage() # requires alias "storage"
127
+ async with storage.transaction() as tx:
128
+ result = await tx.execute("INSERT INTO notes(body) VALUES (?)", (body,))
129
+ rows = await tx.query(
130
+ "SELECT id, body FROM notes WHERE id = ?", (result["lastrowid"],)
131
+ )
132
+ ```
133
+
134
+ `query()` returns a list of row dictionaries. `execute()` returns `rowcount` and
135
+ `lastrowid`; use a transaction plus a query to read a mutation's result, rather
136
+ than SQL `RETURNING`. Bind values as parameters. Put DDL and indexes in migrations.
137
+ The provider manages connections, transaction control and file paths; business
138
+ code does not use `sqlite3.connect`, `ATTACH`, `PRAGMA` or raw `BEGIN`/`COMMIT`.
139
+ Nested and cross-owner transactions are unsupported. Bound query results and
140
+ paginate; do not fetch an entire unbounded history over RPC.
141
+
142
+ Migrations form a linear history: the first has `parent=None`, each later one
143
+ names the preceding revision, and `StoreSchema.revision` is the final revision.
144
+ Read SQL via `importlib.resources.files` and ship it inside the wheel. Applied
145
+ revision names and SQL checksums are immutable. New empty stores initialize
146
+ automatically; existing-store changes use the explicit migration operation.
147
+
148
+ ## Optional resources
149
+
150
+ Publish resources only for requested surfaces. Missing presentation providers
151
+ leave the business service usable. A required service such as storage still
152
+ blocks activation when absent.
153
+
154
+ | Kind | `Resource.data` |
155
+ | --- | --- |
156
+ | `dashboard.page` | `entrypoint="package.page:render"`, optional integer `order`. |
157
+ | `mcp.tool` | `service`, `method`, `description`, `input_schema`, `output_schema`. Keep the schemas consistent with the target method. |
158
+ | `llms.document` | `package` and relative `path` to packaged Markdown. |
159
+
160
+ Use a stable `Resource.id` unique within its kind. Resources belong to their
161
+ plugin generation and are withdrawn when it stops. MCP names are prefixed with
162
+ the plugin ID, for example `sample-notes__list_notes`. Set a query's service
163
+ `MethodSpec.read_only=True`; write methods remain explicit mutations. The first
164
+ MCP provider supports tools, not resources or prompts.
165
+
166
+ Dashboard pages are ordinary synchronous `render(context: PageContext)` functions:
167
+
168
+ ```python
169
+ def render(context):
170
+ import streamlit as st
171
+
172
+ service = context.services.require("sample-notes.notes", version=1)
173
+ result = service.call("list_notes", {"limit": 20})
174
+ st.dataframe(result["notes"])
175
+ ```
176
+
177
+ Put Streamlit in the wheel's `ui` extra and import it in the page. The host resolves
178
+ page contributors together in the Dashboard environment; the business worker
179
+ does not need Streamlit. Page dependencies must coexist. Namespace widget keys
180
+ by plugin ID. Page code uses the public service facade and cannot open worker
181
+ database files. Reload may reset all Dashboard sessions, so retain durable data
182
+ through business services and surface stale-version conflicts to the user.
183
+
184
+ The llms provider publishes an index and the specifically declared documents.
185
+ Write those documents for application users and agents invoking its tools. Keep
186
+ private design notes, credentials and the authoring skill outside those assets.