generated from coulomb/repo-seed
Extension framework optimization
This commit is contained in:
@@ -94,6 +94,58 @@ Subsystem-specific dataclasses may remain richer. The canonical model is the
|
||||
bridge that lets callbacks, registries, diagnostics, provenance, and future
|
||||
policy checks interact consistently.
|
||||
|
||||
### Minimal Runnable Extension
|
||||
|
||||
```python
|
||||
from markitect_tool.extension import (
|
||||
ExtensionDescriptor,
|
||||
ExtensionExecutor,
|
||||
ExtensionRegistry,
|
||||
ProcessingRequest,
|
||||
ProcessingResult,
|
||||
)
|
||||
|
||||
|
||||
def run_example(request: ProcessingRequest) -> ProcessingResult:
|
||||
name = request.input.get("name", "world")
|
||||
return ProcessingResult(output=f"Hello, {name}")
|
||||
|
||||
|
||||
descriptor = ExtensionDescriptor(
|
||||
id="example.hello",
|
||||
kind="example",
|
||||
summary="Small example extension.",
|
||||
factory=lambda: run_example,
|
||||
)
|
||||
|
||||
registry = ExtensionRegistry([descriptor])
|
||||
result = ExtensionExecutor(registry).execute(
|
||||
"example.hello",
|
||||
ProcessingRequest(operation="example.hello", input={"name": "Markitect"}),
|
||||
)
|
||||
```
|
||||
|
||||
Use this executor boundary when callbacks, dependency checks, trace events, or
|
||||
future policy checks matter. For tiny deterministic helpers, it is still fine to
|
||||
keep the existing direct function API and expose a descriptor alongside it.
|
||||
|
||||
### Cache-Key Rules
|
||||
|
||||
`ProcessingRequest.cache_key` includes:
|
||||
|
||||
- operation
|
||||
- input
|
||||
- stable context material
|
||||
- options
|
||||
- scope
|
||||
- declared capabilities
|
||||
- request metadata
|
||||
|
||||
Stable context material includes source path, namespaces, variables, policy, and
|
||||
metadata. It does not include workspace root, caller, or live backend handles.
|
||||
This keeps cache keys portable while avoiding collisions for context-sensitive
|
||||
operations.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
Diagnostics should be:
|
||||
|
||||
@@ -95,6 +95,63 @@ The canonical processing model should define a small set of shared envelopes:
|
||||
Subsystem-specific types may remain richer. The canonical model is the bridge,
|
||||
not a forced replacement for every local dataclass.
|
||||
|
||||
### Processing Request Example
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from markitect_tool.extension import ProcessingContext, ProcessingRequest
|
||||
|
||||
request = ProcessingRequest(
|
||||
operation="query.selector",
|
||||
input={"selector": "sections[heading=Decision]"},
|
||||
context=ProcessingContext(
|
||||
source_path=Path("docs/adr.md"),
|
||||
namespaces={"std": "standards"},
|
||||
variables={"audience": "internal"},
|
||||
),
|
||||
options={"format": "json"},
|
||||
scope="document",
|
||||
)
|
||||
```
|
||||
|
||||
The request cache key includes operation, input, options, scope, declared
|
||||
capabilities, metadata, and stable context semantics:
|
||||
|
||||
- source path
|
||||
- namespaces
|
||||
- variables
|
||||
- policy
|
||||
- metadata
|
||||
|
||||
It intentionally excludes root path, caller name, and live backend handles.
|
||||
Those are execution-environment details. If they matter semantically for an
|
||||
extension, put explicit values in request options or metadata.
|
||||
|
||||
### Processing Result Example
|
||||
|
||||
```python
|
||||
from markitect_tool.extension import (
|
||||
ProcessingProvenance,
|
||||
ProcessingResult,
|
||||
ProcessingTrace,
|
||||
)
|
||||
|
||||
result = ProcessingResult(
|
||||
output={"count": 1},
|
||||
provenance=[
|
||||
ProcessingProvenance(
|
||||
operation="query.selector",
|
||||
source_path="docs/adr.md",
|
||||
content_hash="sha256:...",
|
||||
)
|
||||
],
|
||||
).with_trace(ProcessingTrace(event="query.done"))
|
||||
```
|
||||
|
||||
`ProcessingResult.valid` is derived from diagnostics. Any diagnostic with
|
||||
severity `error` makes the result invalid.
|
||||
|
||||
## Registration Strategy
|
||||
|
||||
Start with in-package registration:
|
||||
@@ -115,6 +172,35 @@ packages become a real requirement.
|
||||
See `docs/extension-authoring.md` for the extension authoring checklist and
|
||||
descriptor template.
|
||||
|
||||
### Registry Use
|
||||
|
||||
Extension registries are optimized for common lookup patterns:
|
||||
|
||||
- `registry.get("backend.local-sqlite")`
|
||||
- `registry.list(kind="query-engine")`
|
||||
- `registry.require_capability("fts")`
|
||||
- `registry.check_dependencies("jsonpath")`
|
||||
|
||||
Kinds and capabilities are indexed at registration time, so large registries can
|
||||
avoid repeated full scans for basic discovery.
|
||||
|
||||
### Execution Lifecycle
|
||||
|
||||
`ExtensionExecutor` wraps a descriptor factory with deterministic lifecycle
|
||||
hooks:
|
||||
|
||||
1. Fetch descriptor.
|
||||
2. Check required optional dependencies.
|
||||
3. Instantiate callable implementation.
|
||||
4. Run `before` callbacks.
|
||||
5. Execute implementation.
|
||||
6. Normalize result type.
|
||||
7. Append `extension.executed` trace.
|
||||
8. Run success or failure callbacks.
|
||||
9. Run final `after` callbacks.
|
||||
|
||||
Callbacks are explicit. The framework does not introduce hidden global behavior.
|
||||
|
||||
## Compatibility Rules
|
||||
|
||||
The refactor must preserve:
|
||||
|
||||
Reference in New Issue
Block a user