@senpi/runtime 3.0.9-dev.20260710204324 → 3.0.9-dev.feat-strategy-compos.20260716233548
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.
- package/dist/cli/composer-commands.d.ts +37 -0
- package/dist/cli/composer-commands.d.ts.map +1 -0
- package/dist/cli/composer-commands.js +396 -0
- package/dist/cli/composer-commands.js.map +1 -0
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +2 -0
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/cli/validate-runtime-config.mjs +190 -0
- package/dist/composer/AUTHORING.md +435 -0
- package/dist/composer/__init__.py +29 -0
- package/dist/composer/authoring/__init__.py +59 -0
- package/dist/composer/authoring/answers.py +297 -0
- package/dist/composer/authoring/archetypes/__init__.py +26 -0
- package/dist/composer/authoring/archetypes/_common.py +410 -0
- package/dist/composer/authoring/archetypes/breakout.py +134 -0
- package/dist/composer/authoring/archetypes/classifier.py +109 -0
- package/dist/composer/authoring/archetypes/skeleton.py +76 -0
- package/dist/composer/authoring/archetypes/thesis_fund.py +103 -0
- package/dist/composer/authoring/archetypes/trend_momentum.py +132 -0
- package/dist/composer/authoring/builder.py +191 -0
- package/dist/composer/authoring/emit.py +264 -0
- package/dist/composer/authoring/gaps.py +115 -0
- package/dist/composer/authoring/generate.py +131 -0
- package/dist/composer/authoring/presets.py +168 -0
- package/dist/composer/authoring/runtime_emit.py +266 -0
- package/dist/composer/authoring/strategy_manifest.py +94 -0
- package/dist/composer/cli.py +487 -0
- package/dist/composer/compiler/__init__.py +22 -0
- package/dist/composer/compiler/compile.py +739 -0
- package/dist/composer/examples/breakout_swing.graph.yaml +121 -0
- package/dist/composer/examples/conviction_scorer.graph.yaml +216 -0
- package/dist/composer/examples/macro_thesis_basket.graph.yaml +247 -0
- package/dist/composer/examples/pre_listing_classifier.graph.yaml +204 -0
- package/dist/composer/examples/trend_momentum_majors.graph.yaml +133 -0
- package/dist/composer/generated/README.md +22 -0
- package/dist/composer/generated/catalog.md +118 -0
- package/dist/composer/generated/describe.md +1168 -0
- package/dist/composer/generated/graph.schema.json +1946 -0
- package/dist/composer/primitives/__init__.py +320 -0
- package/dist/composer/primitives/_candles.py +76 -0
- package/dist/composer/primitives/adapter.py +363 -0
- package/dist/composer/primitives/emitters.py +142 -0
- package/dist/composer/primitives/filters.py +71 -0
- package/dist/composer/primitives/indicators.py +326 -0
- package/dist/composer/primitives/rankers.py +40 -0
- package/dist/composer/primitives/scorers.py +100 -0
- package/dist/composer/primitives/sources.py +979 -0
- package/dist/composer/primitives/stateful.py +254 -0
- package/dist/composer/pure_fn/__init__.py +34 -0
- package/dist/composer/pure_fn/checks.py +39 -0
- package/dist/composer/pure_fn/models.py +177 -0
- package/dist/composer/pure_fn/purity.py +311 -0
- package/dist/composer/pure_fn/runtests.py +155 -0
- package/dist/composer/registry/__init__.py +40 -0
- package/dist/composer/registry/generate.py +425 -0
- package/dist/composer/registry/mcp-tools-snapshot.json +193 -0
- package/dist/composer/registry/mcp_closure.py +311 -0
- package/dist/composer/registry/models.py +417 -0
- package/dist/composer/registry/nodes.py +1297 -0
- package/dist/composer/requirements.txt +21 -0
- package/dist/composer/scaffold_path.py +118 -0
- package/dist/composer/templates/__init__.py +13 -0
- package/dist/composer/templates/scan.py.j2 +34 -0
- package/dist/composer/validator/__init__.py +29 -0
- package/dist/composer/validator/desugar.py +133 -0
- package/dist/composer/validator/errors.py +227 -0
- package/dist/composer/validator/graph_checks.py +443 -0
- package/dist/composer/validator/loader.py +110 -0
- package/dist/composer/validator/schema_checks.py +147 -0
- package/dist/composer/validator/validate.py +86 -0
- package/dist/composer/vendoring.py +103 -0
- package/dist/composer/verbs/__init__.py +49 -0
- package/dist/composer/verbs/_box.py +308 -0
- package/dist/composer/verbs/check.py +254 -0
- package/dist/composer/verbs/close.py +494 -0
- package/dist/composer/verbs/deploy.py +279 -0
- package/dist/composer/verbs/docs.py +30 -0
- package/dist/composer/verbs/fund.py +498 -0
- package/dist/composer/verbs/install.py +586 -0
- package/dist/composer/verbs/runtime_validate.py +422 -0
- package/dist/composer/verbs/smoke.py +134 -0
- package/dist/composer/verbs/status.py +383 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -9
- package/dist/index.js.map +1 -1
- package/dist/runtime/load-runtime-config.d.ts +17 -0
- package/dist/runtime/load-runtime-config.d.ts.map +1 -1
- package/dist/runtime/load-runtime-config.js +2 -2
- package/dist/runtime/load-runtime-config.js.map +1 -1
- package/dist/runtime-api/config.d.ts +2 -2
- package/dist/runtime-api/config.js +4 -4
- package/dist/runtime-api/config.js.map +1 -1
- package/dist/runtime-api/handlers/ingest-signals.d.ts +3 -5
- package/dist/runtime-api/handlers/ingest-signals.d.ts.map +1 -1
- package/dist/runtime-api/handlers/ingest-signals.js +5 -14
- package/dist/runtime-api/handlers/ingest-signals.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +2 -8
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/skills-manager/manifest.d.ts.map +1 -1
- package/dist/skills-manager/manifest.js +1 -3
- package/dist/skills-manager/manifest.js.map +1 -1
- package/dist/skills-manager/skills-manifest.json +1 -3
- package/package.json +4 -2
- package/dist/runtime-api/intake/ingest-mutex.d.ts +0 -16
- package/dist/runtime-api/intake/ingest-mutex.d.ts.map +0 -1
- package/dist/runtime-api/intake/ingest-mutex.js +0 -18
- package/dist/runtime-api/intake/ingest-mutex.js.map +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""schema_checks — stage (c): JSON Schema validation, translated to teaching errors.
|
|
2
|
+
|
|
3
|
+
The registry-generated ``composer/generated/graph.schema.json`` already carries the FULL
|
|
4
|
+
per-node parameter validation (types, bounds, choices, required, unknown-key rejection)
|
|
5
|
+
via its ``allOf`` if/then branches. This module does NOT re-implement any of that — it
|
|
6
|
+
runs the schema with ``jsonschema`` and translates each finding into a located, fix-hinted
|
|
7
|
+
:class:`TeachingError`, so schema violations read the same way as the graph-semantic
|
|
8
|
+
checks layered on top of them.
|
|
9
|
+
|
|
10
|
+
Two deliberate translations:
|
|
11
|
+
* The ``enum`` failure on a node's ``node:`` field (an unknown node type) is SKIPPED
|
|
12
|
+
here — the graph checker emits a far better CMP030 that names the nearest valid
|
|
13
|
+
alternatives. Reporting both would be noise.
|
|
14
|
+
* ``additionalProperties`` inside a ``with:`` block is turned into a per-parameter
|
|
15
|
+
"unknown parameter" error that names the offending key and points at `composer
|
|
16
|
+
describe <node>`.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from jsonschema import Draft202012Validator
|
|
25
|
+
|
|
26
|
+
from .errors import Location, TeachingError
|
|
27
|
+
|
|
28
|
+
_SCHEMA_PATH = (
|
|
29
|
+
Path(__file__).resolve().parent.parent / "generated" / "graph.schema.json"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_schema() -> dict:
|
|
34
|
+
return json.loads(_SCHEMA_PATH.read_text(encoding="utf-8"))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _node_id_at(doc: dict, path) -> tuple[str | None, int | None, dict]:
|
|
38
|
+
"""Given a jsonschema error path, resolve (node_id, none, node_index) context."""
|
|
39
|
+
path = list(path)
|
|
40
|
+
if len(path) >= 2 and path[0] == "nodes" and isinstance(path[1], int):
|
|
41
|
+
idx = path[1]
|
|
42
|
+
try:
|
|
43
|
+
entry = doc["nodes"][idx]
|
|
44
|
+
except (KeyError, IndexError, TypeError):
|
|
45
|
+
return None, None, {}
|
|
46
|
+
if isinstance(entry, dict):
|
|
47
|
+
return entry.get("id"), idx, entry
|
|
48
|
+
return None, None, {}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def schema_errors(doc: dict, line_index: dict) -> list[TeachingError]:
|
|
52
|
+
schema = load_schema()
|
|
53
|
+
validator = Draft202012Validator(schema)
|
|
54
|
+
out: list[TeachingError] = []
|
|
55
|
+
|
|
56
|
+
for err in validator.iter_errors(doc):
|
|
57
|
+
path = list(err.absolute_path)
|
|
58
|
+
node_id, _idx, _entry = _node_id_at(doc, path)
|
|
59
|
+
line = line_index.get("nodes", {}).get(node_id)
|
|
60
|
+
loc = Location(node=node_id, line=line)
|
|
61
|
+
|
|
62
|
+
# Unknown node type: let the graph checker produce the good message.
|
|
63
|
+
if err.validator == "enum" and path and path[-1] == "node":
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
if err.validator == "additionalProperties" and path and path[-1] == "with":
|
|
67
|
+
allowed = set((err.schema or {}).get("properties", {}))
|
|
68
|
+
extra = sorted(k for k in (err.instance or {}) if k not in allowed)
|
|
69
|
+
names = ", ".join(f"`{k}`" for k in extra) or "a parameter"
|
|
70
|
+
node_name = _entry.get("node") if isinstance(_entry, dict) else None
|
|
71
|
+
hint_node = node_name or (node_id or "<node>")
|
|
72
|
+
out.append(
|
|
73
|
+
TeachingError(
|
|
74
|
+
code="CMP010",
|
|
75
|
+
message=f"unknown parameter(s) {names} in `with:`.",
|
|
76
|
+
fix_hint=f"Remove {names}; run `composer describe {hint_node}` to see valid params.",
|
|
77
|
+
location=loc,
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
if err.validator in ("minimum", "maximum"):
|
|
83
|
+
param = path[-1] if path else None
|
|
84
|
+
loc = Location(node=node_id, port=None, line=line)
|
|
85
|
+
bound = err.validator
|
|
86
|
+
limit = err.validator_value
|
|
87
|
+
out.append(
|
|
88
|
+
TeachingError(
|
|
89
|
+
code="CMP011",
|
|
90
|
+
message=f"parameter `{param}` = {err.instance!r} violates {bound} {limit}.",
|
|
91
|
+
fix_hint=f"Set `{param}` to a value with {bound} {limit}.",
|
|
92
|
+
location=loc,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
if err.validator == "type":
|
|
98
|
+
param = path[-1] if path and path[-1] != "with" else None
|
|
99
|
+
want = err.validator_value
|
|
100
|
+
desc = f"parameter `{param}`" if param else "value"
|
|
101
|
+
out.append(
|
|
102
|
+
TeachingError(
|
|
103
|
+
code="CMP012",
|
|
104
|
+
message=f"{desc} = {err.instance!r} must be of type {want}.",
|
|
105
|
+
fix_hint=(
|
|
106
|
+
f"Change `{param}` to a {want} value."
|
|
107
|
+
if param
|
|
108
|
+
else f"Provide a {want} here."
|
|
109
|
+
),
|
|
110
|
+
location=loc,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
if err.validator == "required":
|
|
116
|
+
# e.message like "'x' is a required property"
|
|
117
|
+
missing = err.message.split("'")[1] if "'" in err.message else "a field"
|
|
118
|
+
if node_id or (path and path[0] == "nodes"):
|
|
119
|
+
out.append(
|
|
120
|
+
TeachingError(
|
|
121
|
+
code="CMP013",
|
|
122
|
+
message=f"required parameter `{missing}` is missing.",
|
|
123
|
+
fix_hint=f"Add `{missing}` under this node's `with:` block.",
|
|
124
|
+
location=loc,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
out.append(
|
|
129
|
+
TeachingError(
|
|
130
|
+
code="CMP013",
|
|
131
|
+
message=f"required top-level field `{missing}` is missing.",
|
|
132
|
+
fix_hint=f"Add a top-level `{missing}:` key to the document.",
|
|
133
|
+
location=Location(),
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
# Fallback: any other schema keyword.
|
|
139
|
+
out.append(
|
|
140
|
+
TeachingError(
|
|
141
|
+
code="CMP014",
|
|
142
|
+
message=err.message,
|
|
143
|
+
fix_hint="Adjust the document to satisfy the graph schema.",
|
|
144
|
+
location=loc,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
return out
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""validate — the pipeline that runs the stages and collects every error together.
|
|
2
|
+
|
|
3
|
+
Stage order (each stage's findings are added to the same :class:`ValidationResult`;
|
|
4
|
+
stages do not stop at the first error — the whole point is to report every defect at
|
|
5
|
+
once so the agent repairs in one pass):
|
|
6
|
+
|
|
7
|
+
(a) load + anchor/alias/merge rejection -> loader
|
|
8
|
+
(b) `pipeline:` desugaring into nodes+edges -> desugar (before schema)
|
|
9
|
+
(c) JSON Schema validation (registry-generated) -> schema_checks
|
|
10
|
+
(d) graph-semantic checks -> graph_checks
|
|
11
|
+
(e) pure_fn checks (purity + inline tests) -> composer.pure_fn.checks
|
|
12
|
+
|
|
13
|
+
A hard load failure (a) is terminal — there is no document to check further. Everything
|
|
14
|
+
after that accumulates.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from composer.pure_fn import checks as pure_fn_checks
|
|
22
|
+
|
|
23
|
+
from . import desugar as _desugar
|
|
24
|
+
from . import graph_checks, schema_checks
|
|
25
|
+
from .errors import Location, TeachingError, ValidationResult
|
|
26
|
+
from .loader import AnchorError, YamlLoadError, load_graph
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validate_text(text: str, source: str = "<text>") -> ValidationResult:
|
|
30
|
+
result = ValidationResult(source=source)
|
|
31
|
+
|
|
32
|
+
# (a) load + anchor rejection
|
|
33
|
+
try:
|
|
34
|
+
data, line_index = load_graph(text)
|
|
35
|
+
except AnchorError as exc:
|
|
36
|
+
result.add(
|
|
37
|
+
TeachingError(
|
|
38
|
+
code="CMP002",
|
|
39
|
+
message=f"YAML {exc.what} is not allowed in a strategy graph.",
|
|
40
|
+
fix_hint="Remove the anchor/alias/merge key and write the value out in full (D6 bans them).",
|
|
41
|
+
location=Location(line=exc.line),
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
return result
|
|
45
|
+
except YamlLoadError as exc:
|
|
46
|
+
result.add(
|
|
47
|
+
TeachingError(
|
|
48
|
+
code="CMP001",
|
|
49
|
+
message=f"YAML syntax error: {exc.message}",
|
|
50
|
+
fix_hint="Fix the YAML syntax at the reported line (check indentation and quoting).",
|
|
51
|
+
location=Location(line=exc.line),
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
if not isinstance(data, dict):
|
|
57
|
+
result.add(
|
|
58
|
+
TeachingError(
|
|
59
|
+
code="CMP003",
|
|
60
|
+
message="the document root is empty or not a mapping.",
|
|
61
|
+
fix_hint="A graph is a mapping with at least a `nodes:` list (or a `pipeline:` shorthand).",
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
# (b) desugar pipeline -> nodes+edges (before schema)
|
|
67
|
+
if _desugar.has_pipeline(data):
|
|
68
|
+
data, desugar_errors = _desugar.desugar(data)
|
|
69
|
+
result.extend(desugar_errors)
|
|
70
|
+
|
|
71
|
+
# (c) JSON Schema validation
|
|
72
|
+
result.extend(schema_checks.schema_errors(data, line_index))
|
|
73
|
+
|
|
74
|
+
# (d) graph-semantic checks
|
|
75
|
+
result.extend(graph_checks.check(data, line_index))
|
|
76
|
+
|
|
77
|
+
# (e) pure_fn purity + inline fixture tests (D2 kind 2)
|
|
78
|
+
result.extend(pure_fn_checks.check(data, line_index))
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def validate_file(path: str | Path) -> ValidationResult:
|
|
84
|
+
p = Path(path)
|
|
85
|
+
text = p.read_text(encoding="utf-8")
|
|
86
|
+
return validate_text(text, source=str(p))
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Vendor ``composer.primitives`` into an emitted strategy package as ``strategy_primitives``.
|
|
2
|
+
|
|
3
|
+
The composer compiles a graph into ``scan.py`` + ``runtime.yaml``. On a user's box the
|
|
4
|
+
runtime scaffold puts the SCANNER DIRECTORY on ``sys.path`` and imports ``scan.py`` by module
|
|
5
|
+
name (``src/scanners/external/scaffold/entrypoint_loader.py``) — NOTHING from the composer
|
|
6
|
+
checkout exists there. So the emitted ``scan.py`` must never ``import composer.primitives``;
|
|
7
|
+
it imports ``strategy_primitives``, a verbatim, point-in-time SNAPSHOT of
|
|
8
|
+
``composer/primitives/`` staged as a SIBLING package of ``scan.py`` (same scanner dir → the
|
|
9
|
+
scaffold's ``sys.path`` entry resolves it exactly like ``scan`` itself).
|
|
10
|
+
|
|
11
|
+
This module is the SINGLE place that copy happens, so:
|
|
12
|
+
|
|
13
|
+
* ``composer deploy`` stages a self-contained tree
|
|
14
|
+
(``main/scanners/{scan.py, strategy_primitives/}``);
|
|
15
|
+
* the in-checkout harnesses that EXEC emitted ``scan.py`` (``composer.verbs.smoke``,
|
|
16
|
+
``composer.eval.replay``, the golden-test fixtures) stage the SAME snapshot beside the
|
|
17
|
+
temp ``scan.py`` and put its dir on ``sys.path`` — the on-box layout — so they exercise
|
|
18
|
+
the real vendored package rather than the in-checkout ``composer.primitives``;
|
|
19
|
+
* the deploy manifest hashes every vendored file DETERMINISTICALLY (sorted relpaths).
|
|
20
|
+
|
|
21
|
+
Snapshot semantics are the point: the copy freezes the primitives at deploy/compile time.
|
|
22
|
+
Evolving ``composer/primitives/`` later never mutates an already-staged strategy.
|
|
23
|
+
|
|
24
|
+
All primitives-internal imports are RELATIVE (``from .`` / ``from ._candles``) and no
|
|
25
|
+
primitive references ``composer`` in code, so a verbatim copy under a new package name
|
|
26
|
+
imports cleanly — there are no absolute ``composer.`` imports to rewrite. The
|
|
27
|
+
self-containment test (``python-tests/composer/test_vendored_primitives.py``) guards this by
|
|
28
|
+
importing a STAGED ``scan.py`` in a subprocess where the ``composer`` checkout is NOT
|
|
29
|
+
importable.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import contextlib
|
|
35
|
+
import hashlib
|
|
36
|
+
import sys
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
from typing import Iterator
|
|
39
|
+
|
|
40
|
+
# The package name the emitted scan.py imports (the vendored sibling of scan.py).
|
|
41
|
+
VENDORED_PACKAGE = "strategy_primitives"
|
|
42
|
+
|
|
43
|
+
_PRIMITIVES_DIR = Path(__file__).resolve().parent / "primitives"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _snapshot_files() -> list[Path]:
|
|
47
|
+
"""Every ``.py`` under ``composer/primitives/``, deterministically sorted, ``__pycache__``
|
|
48
|
+
excluded. Stable ordering is what makes the deploy manifest reproducible."""
|
|
49
|
+
return sorted(
|
|
50
|
+
p
|
|
51
|
+
for p in _PRIMITIVES_DIR.rglob("*.py")
|
|
52
|
+
if "__pycache__" not in p.relative_to(_PRIMITIVES_DIR).parts
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def vendor_primitives(dest_dir: str | Path) -> dict[str, str]:
|
|
57
|
+
"""Copy ``composer/primitives/`` VERBATIM into ``<dest_dir>/strategy_primitives/``.
|
|
58
|
+
|
|
59
|
+
Returns an ordered ``{posix_relpath: sha256}`` map over every file written — relpaths are
|
|
60
|
+
``strategy_primitives/<file>`` — for the deploy manifest. Iteration is over
|
|
61
|
+
:func:`_snapshot_files` (sorted), so the returned dict is deterministic. Idempotent:
|
|
62
|
+
re-running overwrites the snapshot with identical bytes.
|
|
63
|
+
"""
|
|
64
|
+
dest = Path(dest_dir)
|
|
65
|
+
pkg_dir = dest / VENDORED_PACKAGE
|
|
66
|
+
hashes: dict[str, str] = {}
|
|
67
|
+
for src in _snapshot_files():
|
|
68
|
+
rel = src.relative_to(_PRIMITIVES_DIR)
|
|
69
|
+
target = pkg_dir / rel
|
|
70
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
data = src.read_bytes()
|
|
72
|
+
target.write_bytes(data)
|
|
73
|
+
hashes[f"{VENDORED_PACKAGE}/{rel.as_posix()}"] = hashlib.sha256(data).hexdigest()
|
|
74
|
+
return hashes
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@contextlib.contextmanager
|
|
78
|
+
def emitted_scan_on_syspath(
|
|
79
|
+
source: str, dest_dir: str | Path, *, module_filename: str = "scan.py"
|
|
80
|
+
) -> Iterator[Path]:
|
|
81
|
+
"""Stage emitted ``scan.py`` + a vendored ``strategy_primitives`` snapshot into ``dest_dir``
|
|
82
|
+
and put ``dest_dir`` on ``sys.path`` for the duration — reproducing the on-box scanner-dir
|
|
83
|
+
layout so ``import strategy_primitives`` resolves exactly as it will in production.
|
|
84
|
+
|
|
85
|
+
Yields the written ``scan.py`` path. Used by the in-checkout harnesses that EXEC emitted
|
|
86
|
+
output; the ``sys.path`` entry is inserted idempotently and removed on exit (the snapshot
|
|
87
|
+
is byte-identical every time, and the primitives package stays cached in ``sys.modules``
|
|
88
|
+
after the first import, so later execs resolve it from cache).
|
|
89
|
+
"""
|
|
90
|
+
dest = Path(dest_dir)
|
|
91
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
vendor_primitives(dest)
|
|
93
|
+
scan_path = dest / module_filename
|
|
94
|
+
scan_path.write_text(source, encoding="utf-8")
|
|
95
|
+
added = str(dest) not in sys.path
|
|
96
|
+
if added:
|
|
97
|
+
sys.path.insert(0, str(dest))
|
|
98
|
+
try:
|
|
99
|
+
yield scan_path
|
|
100
|
+
finally:
|
|
101
|
+
if added:
|
|
102
|
+
with contextlib.suppress(ValueError):
|
|
103
|
+
sys.path.remove(str(dest))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""verbs — the slice-7 composer verbs (check / deploy / status / catalog / describe).
|
|
2
|
+
|
|
3
|
+
Each verb is a thin, pure-ish Python surface producing a structured result with both a
|
|
4
|
+
human ``render()`` and a ``to_json()`` (the shape the TS rim passes through). The lifecycle
|
|
5
|
+
they implement — ``new → check → deploy``, with ``status`` deriving position and naming the
|
|
6
|
+
next verb — is D7; the TS wrapper is verdict-passthrough only (D8).
|
|
7
|
+
|
|
8
|
+
check — ONE verdict aggregating validate + pure_fn tests + compile + fixture smoke.
|
|
9
|
+
deploy — idempotent, HARD-gated on check; stages the deployable artifact set.
|
|
10
|
+
install — copy a staged unit to a canonical hash dir, bind the wallet, hand to the
|
|
11
|
+
frozen `openclaw senpi runtime create` consumer (slice A2).
|
|
12
|
+
close — safe teardown: stop the runtime → confirm gone → trigger MCP strategy_close (B1).
|
|
13
|
+
status — DERIVED lifecycle position + next verb (no journals; the spec file is state),
|
|
14
|
+
extended down the install chain (landed → registered → running) when a box is named.
|
|
15
|
+
docs — catalog index + per-node describe (registry-generated).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .check import CheckResult, check_file, run_check
|
|
19
|
+
from .close import CloseResult, run_close
|
|
20
|
+
from .deploy import DeployResult, deploy_file, run_deploy
|
|
21
|
+
from .docs import catalog_text, describe_text
|
|
22
|
+
from .fund import FundResult, fund_target, run_fund
|
|
23
|
+
from .install import CreateResult, InstallResult, install_target, run_install
|
|
24
|
+
from .smoke import SmokeError, run_smoke
|
|
25
|
+
from .status import StatusResult, run_status
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"CheckResult",
|
|
29
|
+
"run_check",
|
|
30
|
+
"check_file",
|
|
31
|
+
"DeployResult",
|
|
32
|
+
"run_deploy",
|
|
33
|
+
"deploy_file",
|
|
34
|
+
"InstallResult",
|
|
35
|
+
"CreateResult",
|
|
36
|
+
"run_install",
|
|
37
|
+
"install_target",
|
|
38
|
+
"FundResult",
|
|
39
|
+
"run_fund",
|
|
40
|
+
"fund_target",
|
|
41
|
+
"CloseResult",
|
|
42
|
+
"run_close",
|
|
43
|
+
"StatusResult",
|
|
44
|
+
"run_status",
|
|
45
|
+
"catalog_text",
|
|
46
|
+
"describe_text",
|
|
47
|
+
"SmokeError",
|
|
48
|
+
"run_smoke",
|
|
49
|
+
]
|