@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,383 @@
|
|
|
1
|
+
"""status — DERIVED lifecycle position + the next verb to run (D7: state is never journaled).
|
|
2
|
+
|
|
3
|
+
The spec file IS the state (D7). ``composer status`` writes nothing and reads no journal; it
|
|
4
|
+
DERIVES where a graph sits in the ``new → check → deploy → install`` lifecycle from observable
|
|
5
|
+
facts and then NAMES THE NEXT VERB:
|
|
6
|
+
|
|
7
|
+
1. does the graph file exist? (else -> new)
|
|
8
|
+
2. its no-clobber provenance (the content-hash marker `composer new` stamps):
|
|
9
|
+
generated-unedited / hand-edited-since-generated / hand-authored.
|
|
10
|
+
3. the check verdict — RUN it (GREEN/RED). (RED -> fix + check)
|
|
11
|
+
4. staged/deployed artifacts, if an ``--out`` staging dir is given: is there a manifest,
|
|
12
|
+
and does its recorded graph sha match the current file (deployed-current) or not
|
|
13
|
+
(deployed-stale = edited since last deploy)?
|
|
14
|
+
5. THE INSTALL CHAIN, if a ``--state-dir`` is given (B1): walk the current staged content
|
|
15
|
+
DOWN onto the box — landed (canonical hash dir + install.json) → registered
|
|
16
|
+
(installed_runtimes.json) → running (openclaw senpi runtime list, via a seam). All
|
|
17
|
+
derivable from artifacts + one read-only CLI list; multiple hash dirs for one strategy
|
|
18
|
+
are distinguished (which hash is installed/running vs merely landed = a prior version).
|
|
19
|
+
|
|
20
|
+
Positions and their next verb:
|
|
21
|
+
|
|
22
|
+
absent file missing -> composer new
|
|
23
|
+
invalid check RED -> fix errors, then composer check
|
|
24
|
+
valid_undeployed check GREEN, no staged manifest -> composer deploy
|
|
25
|
+
deployed_stale staged, but graph edited since -> composer check, then composer deploy
|
|
26
|
+
deployed_current staged, graph matches, GREEN -> (deployed; nothing to do without a box)
|
|
27
|
+
installable deployed_current, box known, content NOT landed -> composer install
|
|
28
|
+
installed_unregistered landed but not in the box registry (interrupted install) -> composer install
|
|
29
|
+
installed_stopped landed + registered, runtime not running -> composer install (re-run)
|
|
30
|
+
installed_running landed + registered + runtime running -> observe the live tick
|
|
31
|
+
|
|
32
|
+
The last four only occur when ``--state-dir`` is given AND the graph is deployed_current;
|
|
33
|
+
without a box the chain stops at ``deployed_current``.
|
|
34
|
+
|
|
35
|
+
LIVENESS HONESTY: the chain reports the runtime as running/stopped from ``runtime list`` — a
|
|
36
|
+
PROCESS-level fact. It does NOT verify functional liveness (that scan() has actually ticked:
|
|
37
|
+
runCount>0, a fresh lastRunFinishedAt, consecutiveErrorCount==0 — see
|
|
38
|
+
senpi-strategy-ops/references/liveness-verification.md). Those live in ``openclaw senpi state
|
|
39
|
+
-r <id>`` and are reported here as NOT-CHECKED rather than implied — "running" ≠ "operating".
|
|
40
|
+
|
|
41
|
+
You may pass a graph file (``status foo.graph.yaml [--out staged/]``) or a staging directory
|
|
42
|
+
(``status staged/foo/`` — it reads the graph copy + manifest inside).
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import hashlib
|
|
48
|
+
import json
|
|
49
|
+
from dataclasses import dataclass, field
|
|
50
|
+
from pathlib import Path
|
|
51
|
+
from typing import Optional
|
|
52
|
+
|
|
53
|
+
from composer.authoring import emit
|
|
54
|
+
|
|
55
|
+
from . import _box
|
|
56
|
+
from ._box import ListFn, runtime_running, wallet_match
|
|
57
|
+
from .check import run_check
|
|
58
|
+
from .install import INSTALL_JSON, REGISTRY_FILENAME, compute_hash8
|
|
59
|
+
|
|
60
|
+
ABSENT = "absent"
|
|
61
|
+
INVALID = "invalid"
|
|
62
|
+
VALID_UNDEPLOYED = "valid_undeployed"
|
|
63
|
+
DEPLOYED_STALE = "deployed_stale"
|
|
64
|
+
DEPLOYED_CURRENT = "deployed_current"
|
|
65
|
+
INSTALLABLE = "installable"
|
|
66
|
+
INSTALLED_UNREGISTERED = "installed_unregistered"
|
|
67
|
+
INSTALLED_STOPPED = "installed_stopped"
|
|
68
|
+
INSTALLED_RUNNING = "installed_running"
|
|
69
|
+
|
|
70
|
+
# Honest liveness caveat appended to a running install (process-level ≠ functional liveness).
|
|
71
|
+
LIVE_NOT_CHECKED = (
|
|
72
|
+
"runtime process is running; functional liveness NOT checked (scan() runCount / "
|
|
73
|
+
"lastRunFinishedAt / consecutiveErrorCount) — run `openclaw senpi state -r <id> --json`."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
_NEXT = {
|
|
77
|
+
ABSENT: ("composer new", "no graph here — run `composer new <id> --archetype ...` to generate one."),
|
|
78
|
+
INVALID: ("composer check", "graph has errors — fix them (see `composer check`), then re-check."),
|
|
79
|
+
VALID_UNDEPLOYED: ("composer deploy", "graph is GREEN and not yet deployed — run `composer deploy`."),
|
|
80
|
+
DEPLOYED_STALE: ("composer check", "graph edited since last deploy — run `composer check`, then `composer deploy`."),
|
|
81
|
+
DEPLOYED_CURRENT: ("(none)", "deployed and current — pass `--state-dir` to see the on-box install chain, or install it with `composer install`."),
|
|
82
|
+
INSTALLABLE: ("composer install", "deployed and current but NOT on this box — run `composer install <staged-dir> --wallet 0x…`."),
|
|
83
|
+
INSTALLED_UNREGISTERED: ("composer install", "landed on the box but the runtime was never registered (interrupted install) — re-run `composer install` to finish it."),
|
|
84
|
+
INSTALLED_STOPPED: ("composer install", "installed + registered but the runtime is NOT running — re-run `composer install`, or check `openclaw senpi runtime list`."),
|
|
85
|
+
INSTALLED_RUNNING: ("(none)", "installed and running — observe the live tick (`openclaw senpi state -r <id>` for functional liveness)."),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _sha256(text: str) -> str:
|
|
90
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _provenance(text: str) -> str:
|
|
94
|
+
state = emit.is_unedited_generated(text)
|
|
95
|
+
if state is True:
|
|
96
|
+
return "generated_unedited"
|
|
97
|
+
if state is False:
|
|
98
|
+
return "edited"
|
|
99
|
+
return "authored"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class InstallView:
|
|
104
|
+
"""One on-box install of the strategy (one canonical hash dir). ``is_current`` marks the
|
|
105
|
+
hash that matches the currently staged content; others are prior/landed versions."""
|
|
106
|
+
|
|
107
|
+
hash8: str
|
|
108
|
+
wallet: Optional[str] = None
|
|
109
|
+
runtime_id: Optional[str] = None
|
|
110
|
+
is_current: bool = False
|
|
111
|
+
registered: bool = False
|
|
112
|
+
runtime_status: Optional[str] = None # from runtime list: "running"/"stopped"/None(absent)
|
|
113
|
+
running: bool = False
|
|
114
|
+
state: str = "landed" # landed | registered | running
|
|
115
|
+
live: Optional[bool] = None # functional liveness — always None (NOT checked here)
|
|
116
|
+
live_note: Optional[str] = None
|
|
117
|
+
|
|
118
|
+
def to_json(self) -> dict:
|
|
119
|
+
return {
|
|
120
|
+
"hash8": self.hash8,
|
|
121
|
+
"wallet": self.wallet,
|
|
122
|
+
"runtime_id": self.runtime_id,
|
|
123
|
+
"is_current": self.is_current,
|
|
124
|
+
"registered": self.registered,
|
|
125
|
+
"runtime_status": self.runtime_status,
|
|
126
|
+
"running": self.running,
|
|
127
|
+
"state": self.state,
|
|
128
|
+
"live": self.live,
|
|
129
|
+
"live_note": self.live_note,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class StatusResult:
|
|
135
|
+
target: str
|
|
136
|
+
position: str
|
|
137
|
+
exists: bool = False
|
|
138
|
+
provenance: Optional[str] = None
|
|
139
|
+
content_hash: Optional[str] = None
|
|
140
|
+
check_verdict: Optional[str] = None
|
|
141
|
+
check_error_count: int = 0
|
|
142
|
+
staged: bool = False
|
|
143
|
+
staged_dir: Optional[str] = None
|
|
144
|
+
staged_current: Optional[bool] = None
|
|
145
|
+
# install chain (populated only when --state-dir is given)
|
|
146
|
+
install_checked: bool = False
|
|
147
|
+
strategy: Optional[str] = None
|
|
148
|
+
current_hash8: Optional[str] = None
|
|
149
|
+
installs: list[InstallView] = field(default_factory=list)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def next_verb(self) -> str:
|
|
153
|
+
return _NEXT[self.position][0]
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def next_hint(self) -> str:
|
|
157
|
+
return _NEXT[self.position][1]
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def current_install(self) -> Optional[InstallView]:
|
|
161
|
+
return next((i for i in self.installs if i.is_current), None)
|
|
162
|
+
|
|
163
|
+
def to_json(self) -> dict:
|
|
164
|
+
out = {
|
|
165
|
+
"verb": "status",
|
|
166
|
+
"target": self.target,
|
|
167
|
+
"exists": self.exists,
|
|
168
|
+
"position": self.position,
|
|
169
|
+
"provenance": self.provenance,
|
|
170
|
+
"content_hash": self.content_hash,
|
|
171
|
+
"check": {"verdict": self.check_verdict, "error_count": self.check_error_count},
|
|
172
|
+
"deploy": {
|
|
173
|
+
"staged": self.staged,
|
|
174
|
+
"dir": self.staged_dir,
|
|
175
|
+
"current": self.staged_current,
|
|
176
|
+
},
|
|
177
|
+
"next_verb": self.next_verb,
|
|
178
|
+
"next": self.next_hint,
|
|
179
|
+
}
|
|
180
|
+
if self.install_checked:
|
|
181
|
+
out["install"] = {
|
|
182
|
+
"checked": True,
|
|
183
|
+
"strategy": self.strategy,
|
|
184
|
+
"current_hash8": self.current_hash8,
|
|
185
|
+
"installs": [i.to_json() for i in self.installs],
|
|
186
|
+
}
|
|
187
|
+
return out
|
|
188
|
+
|
|
189
|
+
def render(self) -> str:
|
|
190
|
+
lines = [f"status: {self.target}", f" position : {self.position}"]
|
|
191
|
+
if self.exists:
|
|
192
|
+
lines.append(f" provenance : {self.provenance}")
|
|
193
|
+
lines.append(f" check : {self.check_verdict} ({self.check_error_count} error(s))")
|
|
194
|
+
deployed = "yes" if self.staged else "no"
|
|
195
|
+
if self.staged:
|
|
196
|
+
deployed += " (current)" if self.staged_current else " (STALE — edited since)"
|
|
197
|
+
lines.append(f" deployed : {deployed}")
|
|
198
|
+
if self.install_checked:
|
|
199
|
+
if not self.installs:
|
|
200
|
+
lines.append(" installed : no (nothing landed on this box for this strategy)")
|
|
201
|
+
else:
|
|
202
|
+
lines.append(" installed :")
|
|
203
|
+
for i in self.installs:
|
|
204
|
+
tag = "current" if i.is_current else "prior"
|
|
205
|
+
rt = i.runtime_id or "-"
|
|
206
|
+
lines.append(
|
|
207
|
+
f" - {i.hash8} [{tag}] {i.state:<10} runtime={rt} "
|
|
208
|
+
f"(registered={'y' if i.registered else 'n'}, "
|
|
209
|
+
f"runtime_status={i.runtime_status or 'absent'})"
|
|
210
|
+
)
|
|
211
|
+
if i.live_note:
|
|
212
|
+
lines.append(f" liveness: {i.live_note}")
|
|
213
|
+
lines.append(f" next : {self.next_hint}")
|
|
214
|
+
return "\n".join(lines) + "\n"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _strategy_name(text: Optional[str], fallback: str) -> str:
|
|
218
|
+
"""The strategy id — from the graph metadata, else a filename fallback."""
|
|
219
|
+
if text:
|
|
220
|
+
try:
|
|
221
|
+
from composer.validator.loader import load_graph
|
|
222
|
+
doc, _ = load_graph(text)
|
|
223
|
+
name = (doc.get("metadata") or {}).get("strategy")
|
|
224
|
+
if name:
|
|
225
|
+
return str(name)
|
|
226
|
+
except Exception: # noqa: BLE001
|
|
227
|
+
pass
|
|
228
|
+
return fallback.replace(".graph", "")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _resolve(target, out_dir) -> tuple[Optional[str], str, Optional[Path], str]:
|
|
232
|
+
"""Resolve the target into (graph_text|None, label, staged_dir|None, strategy_name).
|
|
233
|
+
|
|
234
|
+
A directory containing a manifest.json is treated as a staging dir (read its graph
|
|
235
|
+
copy). Otherwise the target is a graph file and ``out_dir/<strategy>`` (if given) is the
|
|
236
|
+
staging dir to inspect.
|
|
237
|
+
"""
|
|
238
|
+
p = Path(target)
|
|
239
|
+
if p.is_dir():
|
|
240
|
+
manifest = p / "manifest.json"
|
|
241
|
+
if manifest.is_file():
|
|
242
|
+
man = json.loads(manifest.read_text(encoding="utf-8"))
|
|
243
|
+
strategy = str(man.get("strategy", p.name))
|
|
244
|
+
graph_name = f"{strategy}.graph.yaml"
|
|
245
|
+
# slice 9 package tree: the graph copy lives under the instance dir (main/); fall
|
|
246
|
+
# back to the flat legacy location for older stagings.
|
|
247
|
+
gpath = next(
|
|
248
|
+
(c for c in (p / "main" / graph_name, p / graph_name) if c.is_file()),
|
|
249
|
+
p / "main" / graph_name,
|
|
250
|
+
)
|
|
251
|
+
text = gpath.read_text(encoding="utf-8") if gpath.is_file() else None
|
|
252
|
+
return text, str(gpath if gpath.is_file() else p), p, strategy
|
|
253
|
+
return None, str(p), None, p.name
|
|
254
|
+
if not p.is_file():
|
|
255
|
+
return None, str(p), None, p.name.replace(".graph.yaml", "")
|
|
256
|
+
text = p.read_text(encoding="utf-8")
|
|
257
|
+
strategy = _strategy_name(text, p.stem)
|
|
258
|
+
staged = None
|
|
259
|
+
if out_dir is not None:
|
|
260
|
+
candidate = Path(out_dir) / strategy
|
|
261
|
+
if (candidate / "manifest.json").is_file():
|
|
262
|
+
staged = candidate
|
|
263
|
+
return text, str(p), staged, strategy
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _read_registry_ids(state_dir: Path) -> set:
|
|
267
|
+
"""Runtime ids the box registry lists (install-complete markers; read-only)."""
|
|
268
|
+
try:
|
|
269
|
+
data = json.loads((state_dir / REGISTRY_FILENAME).read_text(encoding="utf-8"))
|
|
270
|
+
except (OSError, json.JSONDecodeError):
|
|
271
|
+
return set()
|
|
272
|
+
return {e.get("id") for e in (data.get("runtimes") or []) if isinstance(e, dict) and e.get("id")}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _derive_installs(state_dir: Path, strategy: str, current_hash8: Optional[str],
|
|
276
|
+
list_fn: ListFn) -> list[InstallView]:
|
|
277
|
+
"""Walk ``<state>/strategies/<strategy>/`` → one InstallView per hash dir (landed →
|
|
278
|
+
registered → running). One read-only ``runtime list`` supplies process status."""
|
|
279
|
+
root = state_dir / "strategies" / strategy
|
|
280
|
+
if not root.is_dir():
|
|
281
|
+
return []
|
|
282
|
+
registry_ids = _read_registry_ids(state_dir)
|
|
283
|
+
runtimes = list_fn()
|
|
284
|
+
views: list[InstallView] = []
|
|
285
|
+
for child in sorted(root.iterdir()):
|
|
286
|
+
inst_json = child / INSTALL_JSON
|
|
287
|
+
if not (child.is_dir() and inst_json.is_file()):
|
|
288
|
+
continue
|
|
289
|
+
try:
|
|
290
|
+
rec = json.loads(inst_json.read_text(encoding="utf-8"))
|
|
291
|
+
except (OSError, json.JSONDecodeError):
|
|
292
|
+
continue
|
|
293
|
+
hash8 = str(rec.get("hash8") or child.name)
|
|
294
|
+
rid = rec.get("runtime_id")
|
|
295
|
+
wallet = rec.get("wallet")
|
|
296
|
+
registered = rid in registry_ids
|
|
297
|
+
# match a runtime row by id or bound wallet.
|
|
298
|
+
rt = next((r for r in runtimes
|
|
299
|
+
if (rid and r.get("name") == rid) or wallet_match(r.get("wallet"), wallet)), None)
|
|
300
|
+
running = bool(rt and runtime_running(rt))
|
|
301
|
+
rt_status = (rt.get("status") if rt else None)
|
|
302
|
+
state = "running" if running else ("registered" if registered else "landed")
|
|
303
|
+
v = InstallView(
|
|
304
|
+
hash8=hash8, wallet=wallet, runtime_id=rid,
|
|
305
|
+
is_current=(current_hash8 is not None and hash8 == current_hash8),
|
|
306
|
+
registered=registered, runtime_status=rt_status, running=running, state=state,
|
|
307
|
+
)
|
|
308
|
+
if running:
|
|
309
|
+
v.live_note = LIVE_NOT_CHECKED # honest: process up, functional liveness unverified
|
|
310
|
+
views.append(v)
|
|
311
|
+
return views
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def run_status(target, out_dir=None, *, state_dir=None,
|
|
315
|
+
list_fn: ListFn = _box.default_list_runtimes) -> StatusResult:
|
|
316
|
+
text, label, staged_dir, strategy = _resolve(target, out_dir)
|
|
317
|
+
|
|
318
|
+
if text is None:
|
|
319
|
+
return StatusResult(target=label, position=ABSENT, exists=False)
|
|
320
|
+
|
|
321
|
+
content_hash = _sha256(text)
|
|
322
|
+
provenance = _provenance(text)
|
|
323
|
+
check = run_check(text, Path(label).name)
|
|
324
|
+
err_count = check.to_json()["error_count"]
|
|
325
|
+
|
|
326
|
+
# deploy facts
|
|
327
|
+
staged = False
|
|
328
|
+
staged_current: Optional[bool] = None
|
|
329
|
+
staged_dir_str: Optional[str] = None
|
|
330
|
+
current_hash8: Optional[str] = None
|
|
331
|
+
if staged_dir is not None:
|
|
332
|
+
manifest_path = staged_dir / "manifest.json"
|
|
333
|
+
try:
|
|
334
|
+
man = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
335
|
+
except (OSError, json.JSONDecodeError):
|
|
336
|
+
man = {}
|
|
337
|
+
staged = True
|
|
338
|
+
staged_dir_str = str(staged_dir)
|
|
339
|
+
staged_current = man.get("graph_sha256") == content_hash
|
|
340
|
+
if man.get("artifacts"):
|
|
341
|
+
current_hash8 = compute_hash8(man) # the content identity install would land
|
|
342
|
+
|
|
343
|
+
if not check.ok:
|
|
344
|
+
position = INVALID
|
|
345
|
+
elif not staged:
|
|
346
|
+
position = VALID_UNDEPLOYED
|
|
347
|
+
elif staged_current:
|
|
348
|
+
position = DEPLOYED_CURRENT
|
|
349
|
+
else:
|
|
350
|
+
position = DEPLOYED_STALE
|
|
351
|
+
|
|
352
|
+
result = StatusResult(
|
|
353
|
+
target=label,
|
|
354
|
+
position=position,
|
|
355
|
+
exists=True,
|
|
356
|
+
provenance=provenance,
|
|
357
|
+
content_hash=content_hash,
|
|
358
|
+
check_verdict=check.verdict,
|
|
359
|
+
check_error_count=err_count,
|
|
360
|
+
staged=staged,
|
|
361
|
+
staged_dir=staged_dir_str,
|
|
362
|
+
staged_current=staged_current,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# (5) THE INSTALL CHAIN — only when a box (state dir) is named. Extends deployed_current
|
|
366
|
+
# DOWN onto the box; other positions still get the install view for visibility.
|
|
367
|
+
if state_dir is not None:
|
|
368
|
+
result.install_checked = True
|
|
369
|
+
result.strategy = strategy
|
|
370
|
+
result.current_hash8 = current_hash8
|
|
371
|
+
result.installs = _derive_installs(Path(state_dir), strategy, current_hash8, list_fn)
|
|
372
|
+
if position == DEPLOYED_CURRENT:
|
|
373
|
+
ci = result.current_install
|
|
374
|
+
if ci is None:
|
|
375
|
+
result.position = INSTALLABLE
|
|
376
|
+
elif not ci.registered:
|
|
377
|
+
result.position = INSTALLED_UNREGISTERED
|
|
378
|
+
elif ci.running:
|
|
379
|
+
result.position = INSTALLED_RUNNING
|
|
380
|
+
else:
|
|
381
|
+
result.position = INSTALLED_STOPPED
|
|
382
|
+
|
|
383
|
+
return result
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAmCH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -49,7 +49,6 @@ import { startApi } from "./runtime-api/lifecycle.js";
|
|
|
49
49
|
import { ingestSignalsHandler } from "./runtime-api/handlers/ingest-signals.js";
|
|
50
50
|
import { createIntakeContext } from "./runtime-api/intake/intake-context.js";
|
|
51
51
|
import { wireRuntime, intakeUrlFor } from "./runtime-api/intake/intake-mount.js";
|
|
52
|
-
import { ingestDispatchMutex, ingestDispatchKey } from "./runtime-api/intake/ingest-mutex.js";
|
|
53
52
|
import { loadRuntimeConfig } from "./runtime/load-runtime-config.js";
|
|
54
53
|
/** Initialize all core features (DSL, risk, health) on the given event bus. */
|
|
55
54
|
export function initCore(bus) {
|
|
@@ -487,14 +486,8 @@ const senpiRuntimePlugin = {
|
|
|
487
486
|
// Trace the sizing-relevant fields the narrowed request carries, so a
|
|
488
487
|
// field dropped at this seam is visible instead of vanishing silently.
|
|
489
488
|
apiLogger.debug(`[intake] ingest req signal_id=${signal.signal_id} scanner=${resolved.scannerName} asset=${signal.asset} direction=${signal.direction} marginPct=${signal.marginPct} leverage=${signal.leverage}`);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
// at a time instead of the engine rejecting all but the first as
|
|
493
|
-
// UNAVAILABLE. Still void — the POST response never blocks on the
|
|
494
|
-
// trade, just on lock ordering.
|
|
495
|
-
const key = ingestDispatchKey(resolved.runtimeId, resolved.scannerName);
|
|
496
|
-
void ingestDispatchMutex
|
|
497
|
-
.withKeyLock(key, () => h.ingestExternalScannerData(request))
|
|
489
|
+
void h
|
|
490
|
+
.ingestExternalScannerData(request)
|
|
498
491
|
.then(() => {
|
|
499
492
|
apiLogger.info(`[intake] ingest ok signal_id=${signal.signal_id} scanner=${resolved.scannerName} asset=${signal.asset} scanner_id=${scannerId}`);
|
|
500
493
|
})
|