@senpi/runtime 3.0.0-dev.runtime-phase-2.20260623210537 → 3.0.0-dev.runtime-phase-2.20260625112547
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/README.md +10 -9
- package/dist/actions/action-runtime-module.d.ts.map +1 -1
- package/dist/actions/action-runtime-module.js +1 -1
- package/dist/actions/action-runtime-module.js.map +1 -1
- package/dist/actions/decision-engine.d.ts.map +1 -1
- package/dist/actions/decision-engine.js +3 -0
- package/dist/actions/decision-engine.js.map +1 -1
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +54 -14
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/actions/types.d.ts +10 -2
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/actions/types.js +8 -0
- package/dist/actions/types.js.map +1 -1
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +80 -74
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +0 -108
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/constants/dsl/index.d.ts +2 -0
- package/dist/constants/dsl/index.d.ts.map +1 -1
- package/dist/constants/dsl/index.js +2 -0
- package/dist/constants/dsl/index.js.map +1 -1
- package/dist/dsl/config/parse-tiers.d.ts.map +1 -1
- package/dist/dsl/config/parse-tiers.js +6 -2
- package/dist/dsl/config/parse-tiers.js.map +1 -1
- package/dist/dsl/engine-client/config-mapping.d.ts +7 -0
- package/dist/dsl/engine-client/config-mapping.d.ts.map +1 -1
- package/dist/dsl/engine-client/config-mapping.js +16 -2
- package/dist/dsl/engine-client/config-mapping.js.map +1 -1
- package/dist/dsl/events/handlers.d.ts +18 -2
- package/dist/dsl/events/handlers.d.ts.map +1 -1
- package/dist/dsl/events/handlers.js +302 -213
- package/dist/dsl/events/handlers.js.map +1 -1
- package/dist/dsl/monitor/create-monitor.d.ts +15 -0
- package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
- package/dist/dsl/monitor/create-monitor.js +46 -9
- package/dist/dsl/monitor/create-monitor.js.map +1 -1
- package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
- package/dist/dsl/monitor/process-one-position.js +138 -77
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/dsl/plugin/index.d.ts +16 -1
- package/dist/dsl/plugin/index.d.ts.map +1 -1
- package/dist/dsl/plugin/index.js +35 -4
- package/dist/dsl/plugin/index.js.map +1 -1
- package/dist/dsl/plugin/options.d.ts +18 -0
- package/dist/dsl/plugin/options.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/create-scanner-module.d.ts +4 -3
- package/dist/runtime/create-scanner-module.d.ts.map +1 -1
- package/dist/runtime/create-scanner-module.js +14 -5
- package/dist/runtime/create-scanner-module.js.map +1 -1
- package/dist/runtime/create-scanner-providers-for-run.d.ts.map +1 -1
- package/dist/runtime/create-scanner-providers-for-run.js +12 -1
- package/dist/runtime/create-scanner-providers-for-run.js.map +1 -1
- package/dist/runtime/load-runtime-config.js +1 -1
- package/dist/runtime/load-runtime-config.js.map +1 -1
- package/dist/runtime/map-strategies-registration.js +7 -8
- package/dist/runtime/map-strategies-registration.js.map +1 -1
- package/dist/runtime/map-strategies.d.ts.map +1 -1
- package/dist/runtime/map-strategies.js +12 -3
- package/dist/runtime/map-strategies.js.map +1 -1
- package/dist/runtime/reconcile-decide.d.ts +73 -0
- package/dist/runtime/reconcile-decide.d.ts.map +1 -0
- package/dist/runtime/reconcile-decide.js +186 -0
- package/dist/runtime/reconcile-decide.js.map +1 -0
- package/dist/runtime/reconcile-run.d.ts +62 -0
- package/dist/runtime/reconcile-run.d.ts.map +1 -0
- package/dist/runtime/reconcile-run.js +173 -0
- package/dist/runtime/reconcile-run.js.map +1 -0
- package/dist/runtime/reconcile-state.d.ts +44 -0
- package/dist/runtime/reconcile-state.d.ts.map +1 -1
- package/dist/runtime/reconcile-state.js +80 -44
- package/dist/runtime/reconcile-state.js.map +1 -1
- package/dist/runtime/risk-guard.d.ts.map +1 -1
- package/dist/runtime/risk-guard.js +17 -12
- package/dist/runtime/risk-guard.js.map +1 -1
- package/dist/runtime/runtime-schema.d.ts +2912 -1052
- package/dist/runtime/runtime-schema.d.ts.map +1 -1
- package/dist/runtime/runtime-schema.js +136 -42
- package/dist/runtime/runtime-schema.js.map +1 -1
- package/dist/runtime/runtime.d.ts +26 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +254 -8
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/scanner-registry.d.ts +1 -1
- package/dist/runtime/scanner-registry.d.ts.map +1 -1
- package/dist/runtime/scanner-registry.js +3 -12
- package/dist/runtime/scanner-registry.js.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
- package/dist/runtime/supervisor/orphan-sweep.js +1 -1
- package/dist/runtime/supervisor/orphan-sweep.js.map +1 -1
- package/dist/runtime/supervisor/supervisor-utils.d.ts +1 -1
- package/dist/runtime/supervisor/supervisor-utils.d.ts.map +1 -1
- package/dist/runtime/supervisor/supervisor-utils.js +1 -1
- package/dist/runtime/supervisor/supervisor-utils.js.map +1 -1
- package/dist/runtime-api/intake/acceptance-record.d.ts +10 -0
- package/dist/runtime-api/intake/acceptance-record.d.ts.map +1 -1
- package/dist/runtime-api/intake/acceptance-record.js +28 -0
- package/dist/runtime-api/intake/acceptance-record.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +28 -36
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts +20 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.js +57 -6
- package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
- package/dist/runtime-api/intake/scanner-launcher.d.ts +18 -4
- package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
- package/dist/runtime-api/intake/scanner-launcher.js +24 -3
- package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
- package/dist/runtime-api/intake/types.d.ts +16 -0
- package/dist/runtime-api/intake/types.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.d.ts +4 -0
- package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.js +12 -0
- package/dist/runtime-api/routes/signals.schema.js.map +1 -1
- package/dist/scanners/external/scaffold/__main__.py +65 -24
- package/dist/scanners/external/scaffold/ctx.py +78 -0
- package/dist/scanners/external/scaffold/delivery.py +56 -22
- package/dist/scanners/external/scaffold/entrypoint_loader.py +42 -48
- package/dist/scanners/external/scaffold/journal.py +13 -2
- package/dist/scanners/external/scaffold/requirements.txt +11 -0
- package/dist/scanners/external/scaffold/scaffold.py +159 -128
- package/dist/scanners/external/scaffold/senpi_mcp.py +250 -0
- package/dist/scanners/external/scaffold/signal_envelope.py +177 -34
- package/dist/scanners/external/scaffold/state_manager.py +175 -0
- package/dist/scanners/external-scanner-receiver.d.ts +6 -0
- package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
- package/dist/scanners/external-scanner-receiver.js +14 -1
- package/dist/scanners/external-scanner-receiver.js.map +1 -1
- package/dist/scripts/run-dsl-standalone-live.d.ts +1 -1
- package/dist/scripts/run-dsl-standalone-live.js +5 -3
- package/dist/scripts/run-dsl-standalone-live.js.map +1 -1
- package/dist/senpi/client.d.ts +37 -4
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +101 -24
- package/dist/senpi/client.js.map +1 -1
- package/dist/state/state-manager.d.ts +17 -0
- package/dist/state/state-manager.d.ts.map +1 -1
- package/dist/state/state-manager.js +50 -4
- package/dist/state/state-manager.js.map +1 -1
- package/dist/strategy/strategy-state.d.ts +9 -1
- package/dist/strategy/strategy-state.d.ts.map +1 -1
- package/dist/strategy/strategy-state.js +62 -9
- package/dist/strategy/strategy-state.js.map +1 -1
- package/dist/tracking/constants.d.ts.map +1 -1
- package/dist/tracking/constants.js +2 -0
- package/dist/tracking/constants.js.map +1 -1
- package/dist/types/dsl/index.d.ts +1 -1
- package/dist/types/dsl/index.d.ts.map +1 -1
- package/dist/types/scanner.d.ts +7 -0
- package/dist/types/scanner.d.ts.map +1 -1
- package/dist/types/strategy.d.ts +3 -1
- package/dist/types/strategy.d.ts.map +1 -1
- package/dist/utils/keyed-mutex.d.ts +37 -0
- package/dist/utils/keyed-mutex.d.ts.map +1 -0
- package/dist/utils/keyed-mutex.js +77 -0
- package/dist/utils/keyed-mutex.js.map +1 -0
- package/examples/strategies/README.md +17 -12
- package/examples/strategies/iguana/README.md +135 -0
- package/examples/strategies/iguana/recipe.yaml +137 -0
- package/examples/strategies/iguana/scanners/scan.py +243 -0
- package/examples/strategies/iguana/scanners/scoring.py +152 -0
- package/examples/strategies/raptor/README.md +156 -0
- package/examples/strategies/raptor/recipe.yaml +154 -0
- package/examples/strategies/raptor/scanners/scan.py +349 -0
- package/examples/strategies/raptor/scanners/scoring.py +452 -0
- package/examples/strategies/spider/README.md +89 -0
- package/examples/strategies/spider/scalp/recipe.yaml +129 -0
- package/examples/strategies/spider/scalp/scanners/scan.py +219 -0
- package/examples/strategies/spider/scalp/scanners/scoring.py +179 -0
- package/examples/strategies/spider/swing/recipe.yaml +137 -0
- package/examples/strategies/spider/swing/scanners/scan.py +334 -0
- package/examples/strategies/spider/swing/scanners/scoring.py +184 -0
- package/examples/strategies/turbine/README.md +166 -0
- package/examples/strategies/turbine/recipe-runners.yaml +123 -0
- package/examples/strategies/turbine/recipe-volume.yaml +129 -0
- package/examples/strategies/turbine/scanners/scan.py +283 -0
- package/examples/strategies/turbine/scanners/scoring.py +190 -0
- package/package.json +1 -1
- package/dist/scanners/external/liquidation-watchdog/README.md +0 -107
- package/dist/scanners/external/liquidation-watchdog/liquidation-watchdog.yaml +0 -48
- package/dist/scanners/external/liquidation-watchdog/producer.mjs +0 -336
- package/dist/scanners/external/momentum/README.md +0 -148
- package/dist/scanners/external/momentum/momentum.yaml +0 -116
- package/dist/scanners/external/momentum/producer.mjs +0 -251
- package/dist/scanners/external/shared/ingest.mjs +0 -112
- package/examples/README.md +0 -89
- package/examples/scanners-consumer-quickstart.mjs +0 -134
- package/examples/scanners-emerging-movers-v4.mjs +0 -287
- package/examples/scanners-liquidation-watchdog.mjs +0 -341
- package/examples/scanners-live-mcp.mjs +0 -208
- package/examples/scanners-multi-strategy.mjs +0 -165
- package/examples/scanners-position-tracker-e2e.mjs +0 -225
- package/examples/scanners-scheduled-events.mjs +0 -96
- package/examples/scanners-senpi-provider-client.mjs +0 -182
- package/examples/scanners-single-strategy.mjs +0 -198
- package/examples/strategies/dsl-showcase.yaml +0 -68
- package/examples/strategies/dsl.yaml +0 -51
- package/examples/strategies/fox-minimal-loosened.yaml +0 -240
- package/examples/strategies/fox-minimal.yaml +0 -242
- package/examples/strategies/minimal.yaml +0 -46
- package/examples/strategies/momentum/how-it-works.md +0 -48
- package/examples/strategies/momentum/momentum-guarded.yaml +0 -161
- package/examples/strategies/risk-showcase.yaml +0 -93
- package/examples/strategies/viper.yaml +0 -67
- package/examples/strategies/wolf.yaml +0 -72
- 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
|
-
/**
|
|
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
|
-
/**
|
|
31
|
+
/** Per-signal `data{}` schema (recipe `signal_data_schema`). */
|
|
32
32
|
fields: Record<string, ExternalFieldDefinition>;
|
|
33
|
-
/**
|
|
34
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
95
|
-
launchCfg.
|
|
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":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAIlC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qDAAqD,CAAC;AAE5F,OAAO,EAAE,MAAM,EAAE,MAAM,8CAA8C,CAAC;
|
|
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;
|
|
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;
|
|
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;
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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 =
|
|
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["
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
102
|
+
journal_path = None
|
|
71
103
|
if state_path:
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
result.append((sig,
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|