@senpi/runtime 3.0.0-dev.runtime-phase-2.20260623210537 → 3.0.1-dev.openclaw-upgrade-mai.20260625140818

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 (215) hide show
  1. package/README.md +10 -9
  2. package/dist/actions/action-runtime-module.d.ts.map +1 -1
  3. package/dist/actions/action-runtime-module.js +1 -1
  4. package/dist/actions/action-runtime-module.js.map +1 -1
  5. package/dist/actions/decision-engine.d.ts.map +1 -1
  6. package/dist/actions/decision-engine.js +3 -0
  7. package/dist/actions/decision-engine.js.map +1 -1
  8. package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
  9. package/dist/actions/open-position/open-position.action.js +54 -14
  10. package/dist/actions/open-position/open-position.action.js.map +1 -1
  11. package/dist/actions/types.d.ts +10 -2
  12. package/dist/actions/types.d.ts.map +1 -1
  13. package/dist/actions/types.js +8 -0
  14. package/dist/actions/types.js.map +1 -1
  15. package/dist/cli/guide-commands.d.ts.map +1 -1
  16. package/dist/cli/guide-commands.js +80 -74
  17. package/dist/cli/guide-commands.js.map +1 -1
  18. package/dist/cli/senpi-commands.d.ts.map +1 -1
  19. package/dist/cli/senpi-commands.js +0 -108
  20. package/dist/cli/senpi-commands.js.map +1 -1
  21. package/dist/constants/dsl/index.d.ts +2 -0
  22. package/dist/constants/dsl/index.d.ts.map +1 -1
  23. package/dist/constants/dsl/index.js +2 -0
  24. package/dist/constants/dsl/index.js.map +1 -1
  25. package/dist/dsl/config/parse-tiers.d.ts.map +1 -1
  26. package/dist/dsl/config/parse-tiers.js +6 -2
  27. package/dist/dsl/config/parse-tiers.js.map +1 -1
  28. package/dist/dsl/engine-client/config-mapping.d.ts +7 -0
  29. package/dist/dsl/engine-client/config-mapping.d.ts.map +1 -1
  30. package/dist/dsl/engine-client/config-mapping.js +16 -2
  31. package/dist/dsl/engine-client/config-mapping.js.map +1 -1
  32. package/dist/dsl/events/handlers.d.ts +18 -2
  33. package/dist/dsl/events/handlers.d.ts.map +1 -1
  34. package/dist/dsl/events/handlers.js +302 -213
  35. package/dist/dsl/events/handlers.js.map +1 -1
  36. package/dist/dsl/monitor/create-monitor.d.ts +15 -0
  37. package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
  38. package/dist/dsl/monitor/create-monitor.js +46 -9
  39. package/dist/dsl/monitor/create-monitor.js.map +1 -1
  40. package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
  41. package/dist/dsl/monitor/process-one-position.js +138 -77
  42. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  43. package/dist/dsl/plugin/index.d.ts +16 -1
  44. package/dist/dsl/plugin/index.d.ts.map +1 -1
  45. package/dist/dsl/plugin/index.js +35 -4
  46. package/dist/dsl/plugin/index.js.map +1 -1
  47. package/dist/dsl/plugin/options.d.ts +18 -0
  48. package/dist/dsl/plugin/options.d.ts.map +1 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +4 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/runtime/create-scanner-module.d.ts +4 -3
  53. package/dist/runtime/create-scanner-module.d.ts.map +1 -1
  54. package/dist/runtime/create-scanner-module.js +14 -5
  55. package/dist/runtime/create-scanner-module.js.map +1 -1
  56. package/dist/runtime/create-scanner-providers-for-run.d.ts.map +1 -1
  57. package/dist/runtime/create-scanner-providers-for-run.js +12 -1
  58. package/dist/runtime/create-scanner-providers-for-run.js.map +1 -1
  59. package/dist/runtime/load-runtime-config.js +1 -1
  60. package/dist/runtime/load-runtime-config.js.map +1 -1
  61. package/dist/runtime/map-strategies-registration.js +7 -8
  62. package/dist/runtime/map-strategies-registration.js.map +1 -1
  63. package/dist/runtime/map-strategies.d.ts.map +1 -1
  64. package/dist/runtime/map-strategies.js +12 -3
  65. package/dist/runtime/map-strategies.js.map +1 -1
  66. package/dist/runtime/reconcile-decide.d.ts +73 -0
  67. package/dist/runtime/reconcile-decide.d.ts.map +1 -0
  68. package/dist/runtime/reconcile-decide.js +186 -0
  69. package/dist/runtime/reconcile-decide.js.map +1 -0
  70. package/dist/runtime/reconcile-run.d.ts +62 -0
  71. package/dist/runtime/reconcile-run.d.ts.map +1 -0
  72. package/dist/runtime/reconcile-run.js +173 -0
  73. package/dist/runtime/reconcile-run.js.map +1 -0
  74. package/dist/runtime/reconcile-state.d.ts +44 -0
  75. package/dist/runtime/reconcile-state.d.ts.map +1 -1
  76. package/dist/runtime/reconcile-state.js +80 -44
  77. package/dist/runtime/reconcile-state.js.map +1 -1
  78. package/dist/runtime/risk-guard.d.ts.map +1 -1
  79. package/dist/runtime/risk-guard.js +17 -12
  80. package/dist/runtime/risk-guard.js.map +1 -1
  81. package/dist/runtime/runtime-schema.d.ts +2912 -1052
  82. package/dist/runtime/runtime-schema.d.ts.map +1 -1
  83. package/dist/runtime/runtime-schema.js +136 -42
  84. package/dist/runtime/runtime-schema.js.map +1 -1
  85. package/dist/runtime/runtime.d.ts +26 -0
  86. package/dist/runtime/runtime.d.ts.map +1 -1
  87. package/dist/runtime/runtime.js +254 -8
  88. package/dist/runtime/runtime.js.map +1 -1
  89. package/dist/runtime/scanner-registry.d.ts +1 -1
  90. package/dist/runtime/scanner-registry.d.ts.map +1 -1
  91. package/dist/runtime/scanner-registry.js +3 -12
  92. package/dist/runtime/scanner-registry.js.map +1 -1
  93. package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
  94. package/dist/runtime/supervisor/long-running-supervisor.js +1 -1
  95. package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
  96. package/dist/runtime/supervisor/orphan-sweep.js +1 -1
  97. package/dist/runtime/supervisor/orphan-sweep.js.map +1 -1
  98. package/dist/runtime/supervisor/supervisor-utils.d.ts +1 -1
  99. package/dist/runtime/supervisor/supervisor-utils.d.ts.map +1 -1
  100. package/dist/runtime/supervisor/supervisor-utils.js +1 -1
  101. package/dist/runtime/supervisor/supervisor-utils.js.map +1 -1
  102. package/dist/runtime-api/intake/acceptance-record.d.ts +10 -0
  103. package/dist/runtime-api/intake/acceptance-record.d.ts.map +1 -1
  104. package/dist/runtime-api/intake/acceptance-record.js +28 -0
  105. package/dist/runtime-api/intake/acceptance-record.js.map +1 -1
  106. package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
  107. package/dist/runtime-api/intake/intake-mount.js +28 -36
  108. package/dist/runtime-api/intake/intake-mount.js.map +1 -1
  109. package/dist/runtime-api/intake/process-signal-post.d.ts +20 -1
  110. package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
  111. package/dist/runtime-api/intake/process-signal-post.js +57 -6
  112. package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
  113. package/dist/runtime-api/intake/scanner-launcher.d.ts +18 -4
  114. package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
  115. package/dist/runtime-api/intake/scanner-launcher.js +24 -3
  116. package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
  117. package/dist/runtime-api/intake/types.d.ts +16 -0
  118. package/dist/runtime-api/intake/types.d.ts.map +1 -1
  119. package/dist/runtime-api/routes/signals.schema.d.ts +4 -0
  120. package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
  121. package/dist/runtime-api/routes/signals.schema.js +12 -0
  122. package/dist/runtime-api/routes/signals.schema.js.map +1 -1
  123. package/dist/scanners/external/scaffold/__main__.py +65 -24
  124. package/dist/scanners/external/scaffold/ctx.py +78 -0
  125. package/dist/scanners/external/scaffold/delivery.py +56 -22
  126. package/dist/scanners/external/scaffold/entrypoint_loader.py +42 -48
  127. package/dist/scanners/external/scaffold/journal.py +13 -2
  128. package/dist/scanners/external/scaffold/requirements.txt +11 -0
  129. package/dist/scanners/external/scaffold/scaffold.py +159 -128
  130. package/dist/scanners/external/scaffold/senpi_mcp.py +250 -0
  131. package/dist/scanners/external/scaffold/signal_envelope.py +177 -34
  132. package/dist/scanners/external/scaffold/state_manager.py +175 -0
  133. package/dist/scanners/external-scanner-receiver.d.ts +6 -0
  134. package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
  135. package/dist/scanners/external-scanner-receiver.js +14 -1
  136. package/dist/scanners/external-scanner-receiver.js.map +1 -1
  137. package/dist/scripts/run-dsl-standalone-live.d.ts +1 -1
  138. package/dist/scripts/run-dsl-standalone-live.js +5 -3
  139. package/dist/scripts/run-dsl-standalone-live.js.map +1 -1
  140. package/dist/senpi/client.d.ts +37 -4
  141. package/dist/senpi/client.d.ts.map +1 -1
  142. package/dist/senpi/client.js +101 -24
  143. package/dist/senpi/client.js.map +1 -1
  144. package/dist/state/state-manager.d.ts +17 -0
  145. package/dist/state/state-manager.d.ts.map +1 -1
  146. package/dist/state/state-manager.js +50 -4
  147. package/dist/state/state-manager.js.map +1 -1
  148. package/dist/strategy/strategy-state.d.ts +9 -1
  149. package/dist/strategy/strategy-state.d.ts.map +1 -1
  150. package/dist/strategy/strategy-state.js +62 -9
  151. package/dist/strategy/strategy-state.js.map +1 -1
  152. package/dist/tracking/constants.d.ts.map +1 -1
  153. package/dist/tracking/constants.js +2 -0
  154. package/dist/tracking/constants.js.map +1 -1
  155. package/dist/types/dsl/index.d.ts +1 -1
  156. package/dist/types/dsl/index.d.ts.map +1 -1
  157. package/dist/types/scanner.d.ts +7 -0
  158. package/dist/types/scanner.d.ts.map +1 -1
  159. package/dist/types/strategy.d.ts +3 -1
  160. package/dist/types/strategy.d.ts.map +1 -1
  161. package/dist/utils/keyed-mutex.d.ts +37 -0
  162. package/dist/utils/keyed-mutex.d.ts.map +1 -0
  163. package/dist/utils/keyed-mutex.js +77 -0
  164. package/dist/utils/keyed-mutex.js.map +1 -0
  165. package/examples/strategies/README.md +17 -12
  166. package/examples/strategies/iguana/README.md +135 -0
  167. package/examples/strategies/iguana/recipe.yaml +137 -0
  168. package/examples/strategies/iguana/scanners/scan.py +243 -0
  169. package/examples/strategies/iguana/scanners/scoring.py +152 -0
  170. package/examples/strategies/raptor/README.md +156 -0
  171. package/examples/strategies/raptor/recipe.yaml +154 -0
  172. package/examples/strategies/raptor/scanners/scan.py +349 -0
  173. package/examples/strategies/raptor/scanners/scoring.py +452 -0
  174. package/examples/strategies/spider/README.md +89 -0
  175. package/examples/strategies/spider/scalp/recipe.yaml +129 -0
  176. package/examples/strategies/spider/scalp/scanners/scan.py +219 -0
  177. package/examples/strategies/spider/scalp/scanners/scoring.py +179 -0
  178. package/examples/strategies/spider/swing/recipe.yaml +137 -0
  179. package/examples/strategies/spider/swing/scanners/scan.py +334 -0
  180. package/examples/strategies/spider/swing/scanners/scoring.py +184 -0
  181. package/examples/strategies/turbine/README.md +166 -0
  182. package/examples/strategies/turbine/recipe-runners.yaml +123 -0
  183. package/examples/strategies/turbine/recipe-volume.yaml +129 -0
  184. package/examples/strategies/turbine/scanners/scan.py +283 -0
  185. package/examples/strategies/turbine/scanners/scoring.py +190 -0
  186. package/openclaw.plugin.json +1 -1
  187. package/package.json +1 -1
  188. package/dist/scanners/external/liquidation-watchdog/README.md +0 -107
  189. package/dist/scanners/external/liquidation-watchdog/liquidation-watchdog.yaml +0 -48
  190. package/dist/scanners/external/liquidation-watchdog/producer.mjs +0 -336
  191. package/dist/scanners/external/momentum/README.md +0 -148
  192. package/dist/scanners/external/momentum/momentum.yaml +0 -116
  193. package/dist/scanners/external/momentum/producer.mjs +0 -251
  194. package/dist/scanners/external/shared/ingest.mjs +0 -112
  195. package/examples/README.md +0 -89
  196. package/examples/scanners-consumer-quickstart.mjs +0 -134
  197. package/examples/scanners-emerging-movers-v4.mjs +0 -287
  198. package/examples/scanners-liquidation-watchdog.mjs +0 -341
  199. package/examples/scanners-live-mcp.mjs +0 -208
  200. package/examples/scanners-multi-strategy.mjs +0 -165
  201. package/examples/scanners-position-tracker-e2e.mjs +0 -225
  202. package/examples/scanners-scheduled-events.mjs +0 -96
  203. package/examples/scanners-senpi-provider-client.mjs +0 -182
  204. package/examples/scanners-single-strategy.mjs +0 -198
  205. package/examples/strategies/dsl-showcase.yaml +0 -68
  206. package/examples/strategies/dsl.yaml +0 -51
  207. package/examples/strategies/fox-minimal-loosened.yaml +0 -240
  208. package/examples/strategies/fox-minimal.yaml +0 -242
  209. package/examples/strategies/minimal.yaml +0 -46
  210. package/examples/strategies/momentum/how-it-works.md +0 -48
  211. package/examples/strategies/momentum/momentum-guarded.yaml +0 -161
  212. package/examples/strategies/risk-showcase.yaml +0 -93
  213. package/examples/strategies/viper.yaml +0 -67
  214. package/examples/strategies/wolf.yaml +0 -72
  215. package/examples/yamls/sample-with-dsl-and-emerging-movers.yaml +0 -123
@@ -24,14 +24,18 @@ export interface ExternalScannerSpec {
24
24
  path: string;
25
25
  /** Function name inside the module that the scaffold calls. */
26
26
  entrypoint: string;
27
- /** Interval in seconds (numeric; already parsed from e.g. "30s"). */
27
+ /** Tick cadence in integer seconds (read directly from recipe `interval_seconds`). */
28
28
  intervalSeconds: number;
29
29
  /** Optional per-tick timeout in seconds. */
30
30
  timeoutSeconds?: number;
31
- /** Field schema passed verbatim from the recipe. */
31
+ /** Per-signal `data{}` schema (recipe `signal_data_schema`). */
32
32
  fields: Record<string, ExternalFieldDefinition>;
33
- /** Optional scanner-specific parameters forwarded to the scaffold. */
34
- params?: Record<string, unknown>;
33
+ /** Author-tunable knobs (recipe `inputs`) passed as the scan() first arg. Optional. */
34
+ inputs?: Record<string, unknown>;
35
+ /** Required fallback signal TTL in integer seconds (recipe `default_signal_validity_seconds`). */
36
+ defaultSignalValiditySeconds: number;
37
+ /** Bound on the persisted state series (recipe `state_history_max_count`); 0/unset = disabled. */
38
+ stateHistoryMaxCount?: number;
35
39
  }
36
40
  /**
37
41
  * The behavioral contract the launcher requires of a supervisor — an
@@ -78,6 +82,16 @@ export interface ScannerLauncherDeps {
78
82
  */
79
83
  launchConfigDir: string;
80
84
  logger: Logger;
85
+ /**
86
+ * MCP creds injected into the scaffold child so ctx.senpi_mcp can reach the
87
+ * server. When unset, the launcher falls back to process.env.SENPI_API_KEY /
88
+ * process.env.SENPI_MCP_URL ("regardless of how the key was supplied").
89
+ * Explicit deps win over the env. Absent/empty values are OMITTED from
90
+ * spec.env — never written as "" — so the Python client stays loud at first
91
+ * use rather than masked by a fabricated empty cred.
92
+ */
93
+ senpiApiKey?: string;
94
+ senpiMcpUrl?: string;
81
95
  /** Default: makeId("scanner"). Inject for deterministic ids in tests. */
82
96
  mintScannerId?: () => string;
83
97
  /** Default: new LongRunningSupervisor(spec, logger). Inject to avoid forking. */
@@ -1 +1 @@
1
- {"version":3,"file":"scanner-launcher.d.ts","sourceRoot":"","sources":["../../../src/runtime-api/intake/scanner-launcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AAEpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qDAAqD,CAAC;AAE3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,eAAe,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAChD,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,QAAQ,EAAE,eAAe,CAAC;IAC1B,uEAAuE;IACvE,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC;IACxB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;;;;;OAQG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IAGf,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IAC7B,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,KAAK,kBAAkB,CAAC;IACjF,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IACxF,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;CAC9C;AAYD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAeyF;IAE9G,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAAS;gBAEjB,OAAO,EAAE,mBAAmB;IAIxC;;;;;;;;;;OAUG;IACH,iBAAiB,IAAI,IAAI;IAkKnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAS5B"}
1
+ {"version":3,"file":"scanner-launcher.d.ts","sourceRoot":"","sources":["../../../src/runtime-api/intake/scanner-launcher.ts"],"names":[],"mappings":"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"}
@@ -43,6 +43,10 @@ export class ScannerLauncher {
43
43
  if (this._initialized)
44
44
  return;
45
45
  const { runtimeId, wallet, scanners, registry, liveness, intakeUrl, scaffoldPackageDir, launchConfigDir, logger, supervisorOptions = {}, } = this.deps;
46
+ // Resolve MCP creds: explicit deps win, else fall back to process.env.
47
+ // Empty/absent values are dropped so we never inject a misleading "".
48
+ const senpiApiKey = this.deps.senpiApiKey || process.env.SENPI_API_KEY || undefined;
49
+ const senpiMcpUrl = this.deps.senpiMcpUrl || process.env.SENPI_MCP_URL || undefined;
46
50
  const mintScannerId = this.deps.mintScannerId ?? (() => makeId("scanner"));
47
51
  const createSupervisor = this.deps.createSupervisor ??
48
52
  ((spec, lg) => new LongRunningSupervisor(spec, lg));
@@ -82,17 +86,30 @@ export class ScannerLauncher {
82
86
  // position), so state and the journal survive a restart.
83
87
  const stateDir = path.join(launchConfigDir, "scanner-state", `${safeName}-${i}`);
84
88
  fs.mkdirSync(stateDir, { recursive: true });
89
+ // Launch-config JSON — the FROZEN launcher↔scaffold key contract (design §4a).
90
+ // Hard cut: `interval` → `interval_seconds`, `params` → `inputs`.
85
91
  const launchCfg = {
86
92
  path: scanner.path,
87
93
  entrypoint: scanner.entrypoint,
88
- interval: scanner.intervalSeconds,
94
+ interval_seconds: scanner.intervalSeconds,
89
95
  state_path: path.join(stateDir, "state.json"),
96
+ default_signal_validity_seconds: scanner.defaultSignalValiditySeconds,
97
+ // Per-signal data{} schema (recipe `signal_data_schema`, held here as
98
+ // `scanner.fields`). The scaffold reads it and validates each signal's
99
+ // `data` scaffold-side, POSTing a violation to /errors for early, loud
100
+ // feedback. Additive — intake keeps its authoritative server-side check.
101
+ signal_data_schema: scanner.fields,
102
+ wallet,
103
+ scanner_name: scanner.name,
90
104
  };
91
105
  if (scanner.timeoutSeconds !== undefined) {
92
106
  launchCfg.timeout_seconds = scanner.timeoutSeconds;
93
107
  }
94
- if (scanner.params !== undefined) {
95
- launchCfg.params = scanner.params;
108
+ if (scanner.inputs !== undefined) {
109
+ launchCfg.inputs = scanner.inputs;
110
+ }
111
+ if (scanner.stateHistoryMaxCount !== undefined) {
112
+ launchCfg.state_history_max_count = scanner.stateHistoryMaxCount;
96
113
  }
97
114
  fs.writeFileSync(cfgPath, JSON.stringify(launchCfg, null, 2), "utf8");
98
115
  // Mutable id holder so the lastAliveAt closure always reads the current id.
@@ -111,6 +128,10 @@ export class ScannerLauncher {
111
128
  EXTERNAL_SCANNER_ID: scannerId,
112
129
  SIGNAL_INGESTION_URL: intakeUrl,
113
130
  PYTHONPATH: pythonPath,
131
+ // Inject MCP creds only when present — omit (never "") when absent so
132
+ // the Python ctx.senpi_mcp client stays loud at first use.
133
+ ...(senpiApiKey ? { SENPI_API_KEY: senpiApiKey } : {}),
134
+ ...(senpiMcpUrl ? { SENPI_MCP_URL: senpiMcpUrl } : {}),
114
135
  },
115
136
  marker: runtimeId,
116
137
  initialDelayMs: i * 250,
@@ -1 +1 @@
1
- {"version":3,"file":"scanner-launcher.js","sourceRoot":"","sources":["../../../src/runtime-api/intake/scanner-launcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scanner-launcher.js","sourceRoot":"","sources":["../../../src/runtime-api/intake/scanner-launcher.ts"],"names":[],"mappings":"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"}
@@ -6,12 +6,28 @@
6
6
  */
7
7
  /** One signal in a POST /signals batch. */
8
8
  export interface IntakeSignal {
9
+ /**
10
+ * Producer-minted idempotency key (the scaffold journal eid). REQUIRED on the
11
+ * wire — it is the dedup key intake uses. It is a SEPARATE id from
12
+ * `correlationId` on PerSignalStatus (which intake mints server-side).
13
+ */
14
+ signal_id: string;
9
15
  asset: string;
10
16
  direction: "LONG" | "SHORT";
11
17
  produced_at: number;
12
18
  valid_until: number;
13
19
  data: Record<string, unknown>;
14
20
  signal_type?: string;
21
+ /**
22
+ * Per-signal sizing, optional. The producer scaffold lifts these to the top
23
+ * level and the open-position action reads them directly. `marginUsd` is the
24
+ * canonical USD margin; `margin` is the legacy alias the consumer still
25
+ * accepts; `leverage` is the per-signal leverage override. All positive when
26
+ * present (the wire schema rejects zero/negative/non-number).
27
+ */
28
+ marginUsd?: number;
29
+ margin?: number;
30
+ leverage?: number;
15
31
  }
16
32
  /** Request body for POST /signals. */
17
33
  export interface SignalsBody {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/runtime-api/intake/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,CAAC;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,0CAA0C;AAC1C,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qCAAqC;AACrC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,iCAAiC;AACjC,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/runtime-api/intake/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,CAAC;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,0CAA0C;AAC1C,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qCAAqC;AACrC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,iCAAiC;AACjC,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -12,12 +12,16 @@
12
12
  export declare const SignalsBodySchema: import("@sinclair/typebox").TObject<{
13
13
  scanner_id: import("@sinclair/typebox").TString;
14
14
  signals: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
15
+ signal_id: import("@sinclair/typebox").TString;
15
16
  asset: import("@sinclair/typebox").TString;
16
17
  direction: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"LONG">, import("@sinclair/typebox").TLiteral<"SHORT">]>;
17
18
  produced_at: import("@sinclair/typebox").TNumber;
18
19
  valid_until: import("@sinclair/typebox").TNumber;
19
20
  data: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnknown>;
20
21
  signal_type: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
22
+ marginUsd: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
23
+ margin: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
24
+ leverage: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
21
25
  }>>;
22
26
  }>;
23
27
  /** TypeBox schema for the /errors request body. */
@@ -1 +1 @@
1
- {"version":3,"file":"signals.schema.d.ts","sourceRoot":"","sources":["../../../src/runtime-api/routes/signals.schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiBH,yDAAyD;AACzD,eAAO,MAAM,iBAAiB;;;;;;;;;;EAM7B,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,gBAAgB;;;EAM5B,CAAC"}
1
+ {"version":3,"file":"signals.schema.d.ts","sourceRoot":"","sources":["../../../src/runtime-api/routes/signals.schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA6BH,yDAAyD;AACzD,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;EAM7B,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,gBAAgB;;;EAM5B,CAAC"}
@@ -11,12 +11,24 @@
11
11
  import { Type } from "@sinclair/typebox";
12
12
  /** TypeBox schema for one signal in an intake /signals batch. */
13
13
  const IntakeSignalSchema = Type.Object({
14
+ // Producer-minted idempotency key (the journal eid). REQUIRED end-to-end: it
15
+ // is the dedup key intake relies on, so a body without it is rejected here at
16
+ // the wire layer. It is a SEPARATE id from `correlationId` (which intake mints
17
+ // server-side and never appears in the request body).
18
+ signal_id: Type.String({ minLength: 1 }),
14
19
  asset: Type.String({ minLength: 1 }),
15
20
  direction: Type.Union([Type.Literal("LONG"), Type.Literal("SHORT")]),
16
21
  produced_at: Type.Number(),
17
22
  valid_until: Type.Number(),
18
23
  data: Type.Record(Type.String(), Type.Unknown()),
19
24
  signal_type: Type.Optional(Type.String()),
25
+ // Per-signal sizing, lifted to the top level by the producer scaffold and read
26
+ // directly by the open-position action. Optional; positive-only matches what the
27
+ // producer emits, so zero / negative / non-number is rejected at the wire
28
+ // (fail-closed). No max here — leverage is clamped downstream by the exchange.
29
+ marginUsd: Type.Optional(Type.Number({ exclusiveMinimum: 0 })),
30
+ margin: Type.Optional(Type.Number({ exclusiveMinimum: 0 })),
31
+ leverage: Type.Optional(Type.Number({ exclusiveMinimum: 0 })),
20
32
  }, { additionalProperties: false });
21
33
  /** TypeBox schema for the full /signals request body. */
22
34
  export const SignalsBodySchema = Type.Object({
@@ -1 +1 @@
1
- {"version":3,"file":"signals.schema.js","sourceRoot":"","sources":["../../../src/runtime-api/routes/signals.schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,iEAAiE;AACjE,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,CACpC;IACE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACpC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE;IAC1B,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE;IAC1B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IAChD,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;CAC1C,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAChC,CAAC;AAEF,yDAAyD;AACzD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAC1C;IACE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACzC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC;CACxC,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAChC,CAAC;AAEF,mDAAmD;AACnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CACzC;IACE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACzC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;CAClD,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAChC,CAAC"}
1
+ {"version":3,"file":"signals.schema.js","sourceRoot":"","sources":["../../../src/runtime-api/routes/signals.schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,iEAAiE;AACjE,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,CACpC;IACE,6EAA6E;IAC7E,8EAA8E;IAC9E,+EAA+E;IAC/E,sDAAsD;IACtD,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACxC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACpC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE;IAC1B,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE;IAC1B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IAChD,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;IACzC,+EAA+E;IAC/E,iFAAiF;IACjF,0EAA0E;IAC1E,+EAA+E;IAC/E,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9D,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC;CAC9D,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAChC,CAAC;AAEF,yDAAyD;AACzD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAC1C;IACE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACzC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC;CACxC,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAChC,CAAC;AAEF,mDAAmD;AACnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CACzC;IACE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACzC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;CAClD,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAChC,CAAC"}
@@ -1,17 +1,25 @@
1
- """Process entry for the scaffold core.
1
+ """Process entry for the scaffold core (v2).
2
2
 
3
3
  Usage: python3 -m scaffold <launch-config.json>
4
4
 
5
- The launch config JSON must contain:
6
- path — directory added to sys.path (the scanner's working directory).
7
- entrypoint — module name to import from `path`.
8
- interval — seconds between tick starts.
9
-
10
- Optional fields:
11
- timeout_seconds — per-tick wall-clock budget (default: interval).
12
- params — tunables dict passed to scan() as the first argument.
13
- state_path — path to the JSON state file.
14
- max_ticks — cap on tick count (for tests).
5
+ The launch config JSON is the FROZEN launcher<->scaffold contract (design §4a).
6
+ This side READS every §4a key the B1 launcher writes and threads them into
7
+ run_scaffold. Hard cut: old key names are gone (`interval` -> `interval_seconds`,
8
+ `params` -> `inputs`).
9
+
10
+ Required keys (missing => fail loud, non-zero exit):
11
+ path — directory added to sys.path (scanner working dir).
12
+ entrypoint — module name to import from `path`.
13
+ interval_seconds — tick cadence in seconds (was `interval`).
14
+ default_signal_validity_seconds — fallback signal TTL (seconds); no magic default.
15
+ wallet — strategy wallet (-> ctx.wallet).
16
+ scanner_name — this scanner's recipe name (-> ctx.scanner_name).
17
+
18
+ Optional keys:
19
+ timeout_seconds — per-tick wall-clock budget (default: interval_seconds).
20
+ state_path — JSON state file path (None => no persistence).
21
+ inputs — tunables dict for scan() (was `params`); default {}.
22
+ state_history_max_count — state series bound; default 0 = history disabled.
15
23
 
16
24
  Delivery sink is wired when EXTERNAL_SCANNER_ID and SIGNAL_INGESTION_URL are set in
17
25
  the environment. If either is absent the scaffold runs with the default no-op sink
@@ -23,15 +31,18 @@ import os
23
31
  import sys
24
32
 
25
33
 
26
- def _main() -> int:
27
- if len(sys.argv) < 2:
34
+ def _main(argv=None) -> int:
35
+ if argv is None:
36
+ argv = sys.argv
37
+
38
+ if len(argv) < 2:
28
39
  print(
29
40
  "Usage: python3 -m scaffold <launch-config.json>",
30
41
  file=sys.stderr,
31
42
  )
32
43
  return 1
33
44
 
34
- config_path = sys.argv[1]
45
+ config_path = argv[1]
35
46
  try:
36
47
  with open(config_path, "r", encoding="utf-8") as fh:
37
48
  config = json.load(fh)
@@ -39,22 +50,43 @@ def _main() -> int:
39
50
  print(f"[scaffold] launch config error: {exc}", file=sys.stderr)
40
51
  return 1
41
52
 
53
+ # ---- Required §4a keys: missing => fail loud (no magic default) ----
42
54
  try:
43
55
  path = config["path"]
44
56
  entrypoint = config["entrypoint"]
45
- interval = float(config["interval"])
57
+ interval = float(config["interval_seconds"])
58
+ default_signal_validity_seconds = int(config["default_signal_validity_seconds"])
59
+ wallet = config["wallet"]
60
+ scanner_name = config["scanner_name"]
46
61
  except (KeyError, TypeError, ValueError) as exc:
47
- print(f"[scaffold] launch config missing required field: {exc}", file=sys.stderr)
62
+ print(
63
+ f"[scaffold] launch config missing/invalid required field: {exc}",
64
+ file=sys.stderr,
65
+ )
48
66
  return 1
49
67
 
68
+ # ---- Optional §4a keys ----
50
69
  timeout_seconds = config.get("timeout_seconds")
51
- params = config.get("params") or {}
70
+ # Hard cut: only `inputs` is read. The legacy `params` key is NOT honored.
71
+ inputs = config.get("inputs") or {}
52
72
  state_path = config.get("state_path")
73
+ state_history_max_count = config.get("state_history_max_count") or 0
74
+ # Per-signal data{} schema (§4a). None / absent => no scaffold-side validation
75
+ # (intake still validates authoritatively). A dict turns on early, loud
76
+ # scaffold-side validation that POSTs a violation to /errors.
77
+ signal_data_schema = config.get("signal_data_schema")
78
+ if signal_data_schema is not None and not isinstance(signal_data_schema, dict):
79
+ signal_data_schema = None
53
80
  max_ticks = config.get("max_ticks")
54
81
 
55
- from scaffold import ScanEntrypointError, ScanSignatureError, run_scaffold
82
+ import scaffold as scaffold_pkg
83
+ from scaffold import ScanEntrypointError, ScanSignatureError
56
84
  from scaffold.loop_primitives import log_event
57
85
 
86
+ # Resolve run_scaffold via the package symbol so a test stub patched onto the
87
+ # package (or this module) is picked up.
88
+ run_scaffold = globals().get("run_scaffold") or scaffold_pkg.run_scaffold
89
+
58
90
  # Build the delivery sink when scanner_id and intake URL are available.
59
91
  sink = None
60
92
  scanner_id = os.environ.get("EXTERNAL_SCANNER_ID", "").strip()
@@ -67,19 +99,24 @@ def _main() -> int:
67
99
  post_timeout = float(os.environ.get("SIGNAL_POST_TIMEOUT_SECONDS", "5"))
68
100
 
69
101
  # Derive journal path from the state file directory when available.
70
- journal_path: str | None = None
102
+ journal_path = None
71
103
  if state_path:
72
- import os as _os
73
- journal_path = _os.path.join(
74
- _os.path.dirname(_os.path.abspath(state_path)),
104
+ journal_path = os.path.join(
105
+ os.path.dirname(os.path.abspath(state_path)),
75
106
  "delivery-journal.jsonl",
76
107
  )
77
108
 
109
+ # §4a threads the per-data-key schema (`signal_data_schema`) to the
110
+ # scaffold so the envelope validates each signal's `data` scaffold-side
111
+ # and POSTs a violation to /errors (early, loud feedback). None ⇒ no
112
+ # scaffold-side validation. The intake still performs authoritative
113
+ # server-side schema validation regardless (this is additive).
78
114
  sink = build_delivery_sink(
79
115
  scanner_id=scanner_id,
80
116
  intake_url=intake_url,
81
- interval=interval,
82
117
  journal_path=journal_path,
118
+ signal_data_schema=signal_data_schema,
119
+ default_signal_validity_seconds=default_signal_validity_seconds,
83
120
  timeout=post_timeout,
84
121
  )
85
122
  log_event("delivery_sink_enabled", scanner_id=scanner_id, intake_url=intake_url)
@@ -98,8 +135,12 @@ def _main() -> int:
98
135
  entrypoint,
99
136
  interval=interval,
100
137
  timeout_seconds=timeout_seconds,
101
- params=params,
138
+ inputs=inputs,
102
139
  state_path=state_path,
140
+ scanner_name=scanner_name,
141
+ wallet=wallet,
142
+ default_signal_validity_seconds=default_signal_validity_seconds,
143
+ state_history_max_count=state_history_max_count,
103
144
  sink=sink,
104
145
  max_ticks=max_ticks,
105
146
  )
@@ -0,0 +1,78 @@
1
+ """The author-facing `ctx` object handed to scan(inputs, ctx).
2
+
3
+ `ScanContext` is the second argument to the author's `scan(inputs, ctx)`. It is
4
+ two tiers (design §2.1):
5
+
6
+ Capabilities you CALL:
7
+ state — the StateManager (B2): a disk-persisted, bounded, transactional
8
+ series. Passed in AS-IS (never wrapped/copied) so author mutations
9
+ and the scaffold's commit/rollback hit the SAME object. May be None
10
+ when history is disabled (state_history_max_count 0/unset).
11
+ senpi_mcp — the MCP client capability. PRESENT but EMPTY this block (None);
12
+ B5 fills it. The slot exists now so B5 is purely additive — no
13
+ structural change to ctx later.
14
+
15
+ Read-only metadata you READ:
16
+ wallet — str, the strategy wallet (routing identity).
17
+ scanner_name — str, this scanner's recipe name.
18
+ interval_seconds — int, tick cadence in seconds.
19
+
20
+ The metadata is FROZEN: mutating, deleting, or adding any attribute raises. The
21
+ author can read `ctx.wallet` but cannot rebind it (or scribble a backdoor onto
22
+ ctx). `state` and `senpi_mcp` are likewise immutable bindings on ctx — the author
23
+ uses them through their own APIs, never by reassigning the slot.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import Any, Optional
29
+
30
+
31
+ class ScanContext:
32
+ """Frozen, read-only metadata + capability handles for scan(inputs, ctx).
33
+
34
+ Construction seam the scaffold uses:
35
+
36
+ ScanContext(
37
+ *,
38
+ state, # a StateManager instance (or None when history disabled)
39
+ wallet: str,
40
+ scanner_name: str,
41
+ interval_seconds: int,
42
+ senpi_mcp=None, # B5 fills this; None/absent this block
43
+ )
44
+ """
45
+
46
+ # Lock the attribute set: no __dict__, so any unknown attribute (mutate / add /
47
+ # delete) raises AttributeError. The five documented slots are the only ones.
48
+ __slots__ = ("state", "senpi_mcp", "wallet", "scanner_name", "interval_seconds")
49
+
50
+ def __init__(
51
+ self,
52
+ *,
53
+ state: Any,
54
+ wallet: str,
55
+ scanner_name: str,
56
+ interval_seconds: int,
57
+ senpi_mcp: Optional[Any] = None,
58
+ ) -> None:
59
+ # Bypass our own frozen __setattr__ to populate the slots once at
60
+ # construction. object.__setattr__ writes the slot directly.
61
+ object.__setattr__(self, "state", state)
62
+ object.__setattr__(self, "senpi_mcp", senpi_mcp)
63
+ object.__setattr__(self, "wallet", wallet)
64
+ object.__setattr__(self, "scanner_name", scanner_name)
65
+ object.__setattr__(self, "interval_seconds", interval_seconds)
66
+
67
+ # -- frozen: block every post-construction mutation --------------------
68
+
69
+ def __setattr__(self, name: str, value: Any) -> None:
70
+ raise AttributeError(
71
+ f"ctx is read-only: cannot set {name!r}. The metadata "
72
+ "(wallet/scanner_name/interval_seconds) is frozen, and the "
73
+ "capability slots (state/senpi_mcp) are used through their own APIs, "
74
+ "never reassigned."
75
+ )
76
+
77
+ def __delattr__(self, name: str) -> None:
78
+ raise AttributeError(f"ctx is read-only: cannot delete {name!r}.")
@@ -4,8 +4,9 @@ build_delivery_sink(
4
4
  *,
5
5
  scanner_id: str,
6
6
  intake_url: str,
7
- interval: float, # seconds; valid_until default = produced_at + 2*interval
8
7
  journal_path: str | None,
8
+ signal_data_schema: dict | None, # per-data-key {type, required?}; None = no validation
9
+ default_signal_validity_seconds: int, # fallback signal TTL (seconds)
9
10
  timeout: float = 5.0,
10
11
  clock: Callable[[], float] = time.time,
11
12
  ) -> sink
@@ -32,6 +33,7 @@ from __future__ import annotations
32
33
 
33
34
  import sys
34
35
  import time
36
+ import uuid
35
37
  from typing import Any, Callable
36
38
 
37
39
  from .http_post import ERRORS_PATH, SIGNALS_PATH, TransportError, post_json
@@ -47,14 +49,14 @@ def build_delivery_sink(
47
49
  *,
48
50
  scanner_id: str,
49
51
  intake_url: str,
50
- interval: float,
51
52
  journal_path: str | None,
53
+ signal_data_schema: dict | None,
54
+ default_signal_validity_seconds: int,
52
55
  timeout: float = 5.0,
53
56
  clock: Callable[[], float] = time.time,
54
57
  ):
55
58
  """Factory: build and return the delivery sink callable."""
56
59
 
57
- interval_ms = int(interval * 1000)
58
60
  journal = Journal(journal_path)
59
61
  replayed = [False] # mutable cell; replay runs once on first invocation
60
62
 
@@ -87,16 +89,35 @@ def build_delivery_sink(
87
89
  log_event("delivery_dropped_expired", entry_id=entry.id, asset=entry.asset)
88
90
  return to_send
89
91
 
92
+ def _on_reject(reason: dict) -> None:
93
+ """Loud rejection: log AND POST to /errors (design §4 — never a silent drop)."""
94
+ log_event("delivery_candidate_invalid", **reason)
95
+ _post_error({"type": "candidate_rejected", **reason})
96
+
90
97
  def _wrap_candidates(candidates, produced_at_ms: int) -> list[tuple]:
91
- """Wrap candidates into (signal, entry_id) pairs, skipping invalid ones."""
98
+ """Wrap candidates into (signal, entry_id) pairs, skipping invalid ones.
99
+
100
+ Schema-rejected candidates are NOT silently dropped: candidate_to_signal
101
+ calls _on_reject, which logs AND POSTs to /errors.
102
+ """
92
103
  result = []
93
104
  for c in candidates:
94
- sig = candidate_to_signal(c, produced_at_ms, interval_ms)
105
+ # Mint the stable signal_id (the journal eid) up front and thread it
106
+ # through the envelope so the POSTed signal carries it. record_produced
107
+ # re-uses this same id as the entry id, so wire signal_id == journal eid.
108
+ eid = uuid.uuid4().hex
109
+ sig = candidate_to_signal(
110
+ c,
111
+ produced_at_ms,
112
+ signal_id=eid,
113
+ signal_data_schema=signal_data_schema,
114
+ default_signal_validity_seconds=default_signal_validity_seconds,
115
+ on_reject=_on_reject,
116
+ )
95
117
  if sig is None:
96
- log_event("delivery_candidate_invalid", candidate=repr(c))
97
118
  continue
98
- eid = journal.record_produced(sig, now_ms=produced_at_ms)
99
- result.append((sig, eid))
119
+ entry_id = journal.record_produced(sig, now_ms=produced_at_ms)
120
+ result.append((sig, entry_id))
100
121
  return result
101
122
 
102
123
  def _post_signals(signals: list[dict]) -> tuple[int, Any]:
@@ -125,20 +146,33 @@ def build_delivery_sink(
125
146
  if not isinstance(per_signal, list):
126
147
  per_signal = []
127
148
 
128
- for i, eid in enumerate(pending_eids):
129
- if i < len(per_signal):
130
- sig_status = per_signal[i]
131
- st = sig_status.get("status") if isinstance(sig_status, dict) else None
132
- if st == "accepted":
133
- journal.record_outcome(eid, "delivered", correlationId=sig_status.get("correlationId", ""))
134
- elif st == "rejected":
135
- journal.record_outcome(eid, "rejected", reason=sig_status.get("reason", ""))
136
- elif st in ("expired", "superseded"):
137
- journal.record_outcome(eid, st)
138
- else:
139
- # Unknown status — treat as undelivered (no outcome written)
140
- log_event("delivery_unknown_status", entry_id=eid, status=st)
141
- # If no per-signal status returned for this entry, leave it pending (retry)
149
+ # Match per-signal outcomes BY signal_id, NEVER by array position. The wire
150
+ # signal_id IS the journal entry id, so a pending eid set is the lookup key.
151
+ # Index-matching would cross-attribute outcomes (asset A's "accepted"
152
+ # recorded against asset B) under out-of-order or partial responses —
153
+ # capital-sensitive. Robust to: out-of-order, partial (fewer statuses than
154
+ # signals), and unknown/missing signal_id (ignored safely, never applied by
155
+ # position). An unmatched entry stays pending and is retried.
156
+ pending = set(pending_eids)
157
+ for sig_status in per_signal:
158
+ if not isinstance(sig_status, dict):
159
+ continue
160
+ eid = sig_status.get("signal_id")
161
+ # Missing or unknown signal_id: cannot be matched -> ignore safely.
162
+ if not isinstance(eid, str) or eid not in pending:
163
+ log_event("delivery_status_unmatched", signal_id=eid)
164
+ continue
165
+ st = sig_status.get("status")
166
+ if st == "accepted":
167
+ journal.record_outcome(eid, "delivered", correlationId=sig_status.get("correlationId", ""))
168
+ elif st == "rejected":
169
+ journal.record_outcome(eid, "rejected", reason=sig_status.get("reason", ""))
170
+ elif st in ("expired", "superseded"):
171
+ journal.record_outcome(eid, st)
172
+ else:
173
+ # Unknown status — treat as undelivered (no outcome written)
174
+ log_event("delivery_unknown_status", entry_id=eid, status=st)
175
+ # Entries with no matching status returned stay pending (retry next tick).
142
176
 
143
177
  return True
144
178