@winkjs/composer 0.5.1 → 0.7.0
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 +1 -1
- package/package.json +3 -3
- package/src/composer.js +1 -1
- package/src/core/batch/aoo-encoder.js +4 -4
- package/src/core/batch/sliding-window.js +2 -2
- package/src/core/codec/index.js +4 -4
- package/src/core/emitter-manager/mqtt/connection.js +209 -0
- package/src/core/emitter-manager/mqtt/constants.js +43 -29
- package/src/core/emitter-manager/mqtt/emitter.js +108 -633
- package/src/core/emitter-manager/mqtt/health.js +151 -0
- package/src/core/emitter-manager/mqtt/index.js +68 -4
- package/src/core/emitter-manager/mqtt/publish.js +292 -0
- package/src/core/emitter-manager/mqtt/resolve-config.js +312 -0
- package/src/core/emitter-manager/mqtt/shutdown-drain.js +247 -0
- package/src/core/emitter-manager/terminal/assert-columns.js +1 -1
- package/src/core/emitter-manager/terminal/index.js +20 -5
- package/src/core/env-vars.js +143 -16
- package/src/core/logger/index.js +174 -0
- package/src/core/mqtt-protocol.js +2 -2
- package/src/core/partition-manager/get-stats.js +31 -0
- package/src/core/partition-manager/index.js +1 -0
- package/src/core/partition-manager/init.js +15 -0
- package/src/core/partition-manager/update.js +143 -87
- package/src/core/partition-manager/validate-flow.js +1 -1
- package/src/core/semantics/loader.js +1 -1
- package/src/core/semantics/warnings.js +5 -3
- package/src/core/shutdown-manager/index.js +71 -21
- package/src/core/source-manager/csv/start.js +109 -35
- package/src/core/source-manager/mqtt/client.js +276 -83
- package/src/core/source-manager/mqtt/dedup.js +19 -14
- package/src/core/source-manager/mqtt/index.js +33 -6
- package/src/core/source-manager/mqtt/status.js +211 -46
- package/src/core/source-manager/record-shape.js +55 -0
- package/src/core/source-manager/test-harness/field-generator.js +1 -1
- package/src/core/source-manager/test-harness/start.js +52 -10
- package/src/core/source-manager/test-harness/validate.js +1 -1
- package/src/core/storage-manager/questdb/address-policy.js +264 -0
- package/src/core/storage-manager/questdb/assert-columns.js +2 -2
- package/src/core/storage-manager/questdb/delivery-gate.js +218 -0
- package/src/core/storage-manager/questdb/ensure-tables.js +1 -1
- package/src/core/storage-manager/questdb/flush-engine.js +852 -0
- package/src/core/storage-manager/questdb/flush-tracker.js +331 -0
- package/src/core/storage-manager/questdb/index.js +592 -827
- package/src/core/storage-manager/questdb/persist-plan.js +147 -162
- package/src/core/storage-manager/questdb/resolve-options.js +403 -0
- package/src/core/storage-manager/questdb/shutdown-drain.js +181 -0
- package/src/core/storage-manager/questdb/skip-warnings.js +180 -0
- package/src/core/test-utils/tcp-proxy.js +194 -1
- package/src/core/tunable/helpers.js +1 -1
- package/src/core/utils/address/index.js +362 -0
- package/src/core/utils/address/probe.js +210 -0
- package/src/core/utils/callback-guard/index.js +176 -0
- package/src/core/utils/clock/index.js +34 -0
- package/src/core/utils/half-life/index.js +4 -4
- package/src/core/utils/jitter/index.js +51 -0
- package/src/core/utils/line-rate/index.js +104 -0
- package/src/core/utils/quantize/index.js +43 -19
- package/src/core/utils/validate/composer-validators.js +2 -2
- package/src/core/utils/validate/index.js +1 -1
- package/src/core/utils/validate/validate.js +1 -1
- package/src/core/wiring/assert-handle.js +5 -5
- package/src/core/wiring/assert-module.js +2 -2
- package/src/core/wiring/wire-emitters.js +7 -4
- package/src/core/wiring/wire-linear-graph.js +1 -1
- package/src/core/wiring/wire-node.js +6 -6
- package/src/core/wiring/wire-semantics.js +1 -1
- package/src/core/wiring/wire-storages.js +14 -5
- package/src/flow/driver.js +31 -6
- package/src/flow/expand-single-input-array.js +4 -4
- package/src/flow/flow-api-schemas.js +4 -4
- package/src/flow/flow.js +62 -41
- package/src/flow/for-each.js +12 -11
- package/src/flow/get-target-array.js +2 -2
- package/src/flow/load-node-module.js +1 -1
- package/src/flow/make-node-method.js +4 -4
- package/src/flow/resolve-pick-by-field.js +1 -1
- package/src/flow/run.js +200 -40
- package/src/flow/serialize.js +1 -1
- package/src/flow/spec-builder.js +1 -1
- package/src/flow/validate.js +1 -1
- package/src/nodes/butterworth-filter/init.js +5 -4
- package/src/nodes/categorize/update.js +3 -1
- package/src/nodes/controller/update.js +2 -1
- package/src/nodes/dwell-time-tracker/update.js +2 -1
- package/src/nodes/emit-if/helpers.js +57 -11
- package/src/nodes/emit-if/init.js +1 -1
- package/src/nodes/emit-if/update.js +8 -11
- package/src/nodes/page-hinkley/update.js +2 -1
- package/src/nodes/pass-if/update.js +3 -1
- package/src/nodes/persist-if/helpers.js +60 -4
- package/src/nodes/persist-if/init.js +1 -1
- package/src/nodes/persist-if/update.js +8 -16
- package/src/nodes/persistence-check/update.js +2 -1
- package/src/nodes/sanitize/update.js +4 -2
- package/src/nodes/swing-watch/update.js +3 -2
- package/src/nodes/threshold/update.js +2 -1
- package/src/nodes/trend/compute-confidence.js +4 -4
- package/src/nodes/trend/update.js +2 -1
- package/src/nodes/winnow/update.js +3 -1
- package/src/tools/training/features.js +13 -13
- package/src/tools/training/logistic.js +1 -1
- package/src/tools/training/metrics.js +2 -2
- package/src/tools/training/scale.js +2 -2
- package/src/tools/training/validate.js +7 -7
- package/src/core/utils/console-formatter/index.js +0 -207
- package/src/nodes/archive/csv/index.js +0 -6
- package/src/nodes/archive/csv/init.js +0 -36
- package/src/nodes/archive/csv/load.js +0 -70
- package/src/nodes/archive/csv/reset.js +0 -14
- package/src/nodes/archive/csv/simple-parser.js +0 -216
- package/src/nodes/archive/csv/test.js +0 -173
- package/src/nodes/archive/csv/update.js +0 -63
- package/src/nodes/sse-emitter/index.js +0 -7
- package/src/nodes/sse-emitter/init.js +0 -105
- package/src/nodes/sse-emitter/introspect.js +0 -134
- package/src/nodes/sse-emitter/publish-to.js +0 -6
- package/src/nodes/sse-emitter/recompute.js +0 -16
- package/src/nodes/sse-emitter/reset.js +0 -19
- package/src/nodes/sse-emitter/update.js +0 -69
- package/src/nodes/sse-emitter/utils/broadcast-heartbeat.js +0 -36
- package/src/nodes/sse-emitter/utils/build-message.js +0 -31
- package/src/nodes/sse-emitter/utils/connection-manager.js +0 -45
- package/src/nodes/sse-emitter/utils/endpoint-initializer.js +0 -20
- package/src/nodes/sse-emitter/utils/health-monitor.js +0 -38
- package/src/nodes/sse-emitter/utils/qos-handler.js +0 -41
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* and the status-signal path below.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { logger } from '../../core/logger/index.js';
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Static fallback error the framework substitutes when an adapter breaks
|
|
11
13
|
* the return contract — no result object at all, or `{ ok: false }` with
|
|
@@ -54,10 +56,52 @@ const recordEmissionFailure = function ( state, error ) {
|
|
|
54
56
|
state.emitErrorLogged = true;
|
|
55
57
|
state.firstEmissionError = error.message;
|
|
56
58
|
state.firstEmissionErrorCode = error.code;
|
|
57
|
-
|
|
59
|
+
logger.error( state.emitErrorLogPrefix + error.code + '): ' + error.message );
|
|
58
60
|
}
|
|
59
61
|
}; // recordEmissionFailure()
|
|
60
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Substituted error for an emitter that THREW from publishNow — the
|
|
65
|
+
* other face of a broken return contract (ADR-018: the hot path never
|
|
66
|
+
* throws; a sink answers { ok }). Same code as the malformed-result
|
|
67
|
+
* face: the remediation is identical (fix the non-conforming adapter),
|
|
68
|
+
* and the message names the difference. Allocates on the failure path
|
|
69
|
+
* only.
|
|
70
|
+
*
|
|
71
|
+
* @param {Error} error - The thrown value
|
|
72
|
+
* @returns {Object} `{ code, message }` for the failure episode
|
|
73
|
+
*/
|
|
74
|
+
const thrownSinkError = function ( error ) {
|
|
75
|
+
return {
|
|
76
|
+
code: 'MALFORMED_RESULT',
|
|
77
|
+
message: `emitter publishNow threw instead of returning { ok, error? }: ${error.message}`
|
|
78
|
+
};
|
|
79
|
+
}; // thrownSinkError()
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Delivers one payload to the wired emitter with the sink call fully
|
|
83
|
+
* guarded. A conforming emitter never throws; a throwing one is a
|
|
84
|
+
* broken adapter, recorded in the emission episode — never blamed on
|
|
85
|
+
* the user's predicate, and never escaped into the pipeline where it
|
|
86
|
+
* would cost the whole message. Returns true only on `{ ok: true }`.
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} state - Node state containing emitter and topic
|
|
89
|
+
* @param {Object} data - Payload to publish
|
|
90
|
+
* @returns {boolean} True when the emitter accepted the payload
|
|
91
|
+
*/
|
|
92
|
+
const deliverToEmitter = function ( state, data ) {
|
|
93
|
+
try {
|
|
94
|
+
const result = state.emitter.publishNow( state.topic, data );
|
|
95
|
+
if ( result && result.ok ) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
recordEmissionFailure( state, ( result && result.error ) || MALFORMED_RESULT_ERROR );
|
|
99
|
+
} catch ( sinkError ) {
|
|
100
|
+
recordEmissionFailure( state, thrownSinkError( sinkError ) );
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}; // deliverToEmitter()
|
|
104
|
+
|
|
61
105
|
/**
|
|
62
106
|
* Emits disable/enable status signal to the wired emitter. Published
|
|
63
107
|
* unconditionally (no connectivity pre-check per ADR-018 — during a
|
|
@@ -80,15 +124,17 @@ const emitStatusSignal = function ( state, disable, reason ) {
|
|
|
80
124
|
$timestamp: Date.now()
|
|
81
125
|
};
|
|
82
126
|
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
if ( result && result.ok ) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
recordEmissionFailure( state, ( result && result.error ) || MALFORMED_RESULT_ERROR );
|
|
127
|
+
// Fully guarded delivery: this path can run inside update()'s
|
|
128
|
+
// catch block, so a throwing emitter here would otherwise escape
|
|
129
|
+
// update() and kill the pipeline. deliverToEmitter contains both
|
|
130
|
+
// faces of a broken adapter — a throw and a malformed result.
|
|
131
|
+
deliverToEmitter( state, signal );
|
|
92
132
|
}; // emitStatusSignal()
|
|
93
133
|
|
|
94
|
-
export {
|
|
134
|
+
export {
|
|
135
|
+
assertAnnotateReturn,
|
|
136
|
+
emitStatusSignal,
|
|
137
|
+
recordEmissionFailure,
|
|
138
|
+
deliverToEmitter,
|
|
139
|
+
MALFORMED_RESULT_ERROR
|
|
140
|
+
};
|
|
@@ -59,7 +59,7 @@ const init = function ( spec ) {
|
|
|
59
59
|
state.firstEmissionErrorCode = null;
|
|
60
60
|
// Static log prefix pre-built here per ADR-004 (no string building in
|
|
61
61
|
// update beyond the two runtime fields, and only once per episode).
|
|
62
|
-
state.emitErrorLogPrefix = `
|
|
62
|
+
state.emitErrorLogPrefix = `winkComposer/emitIf: publish failed (node=${state.name}, insightType=${state.insightType}, code=`;
|
|
63
63
|
|
|
64
64
|
return state;
|
|
65
65
|
}; // init()
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* the two output gates share one failure model).
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { assertAnnotateReturn, emitStatusSignal,
|
|
17
|
+
import { assertAnnotateReturn, emitStatusSignal, deliverToEmitter } from './helpers.js';
|
|
18
|
+
import { logger } from '../../core/logger/index.js';
|
|
18
19
|
|
|
19
20
|
const update = function ( state, msg ) {
|
|
20
21
|
// Always increment pass count for observability
|
|
@@ -44,19 +45,15 @@ const update = function ( state, msg ) {
|
|
|
44
45
|
// Per the ADR-018 sink return contract, the emitter answers
|
|
45
46
|
// { ok: true } on success or
|
|
46
47
|
// { ok: false, error: { code, message } } on failure.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
// deliverToEmitter guards the call itself too: a THROWING
|
|
49
|
+
// emitter is the other face of a broken contract, recorded
|
|
50
|
+
// in the same adapter episode — a non-conforming adapter
|
|
51
|
+
// can neither fail silently nor throw out of the hot path.
|
|
52
|
+
if ( deliverToEmitter( state, data ) ) {
|
|
50
53
|
state.emissionCount += 1;
|
|
51
54
|
state.lastEmissionTime = Date.now();
|
|
52
55
|
// Recovery: a successful publish closes the failure episode.
|
|
53
56
|
if ( state.emitErrorLogged ) state.emitErrorLogged = false;
|
|
54
|
-
} else {
|
|
55
|
-
// A malformed result (no object at all, or ok:false with no
|
|
56
|
-
// error) counts as a failure with the static fallback error,
|
|
57
|
-
// so a non-conforming adapter can neither fail silently
|
|
58
|
-
// nor throw out of the hot path.
|
|
59
|
-
recordEmissionFailure( state, ( result && result.error ) || MALFORMED_RESULT_ERROR );
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
|
|
@@ -76,7 +73,7 @@ const update = function ( state, msg ) {
|
|
|
76
73
|
// Log first error per episode; suppress subsequent until recovery
|
|
77
74
|
if ( !state.predicateErrorLogged ) {
|
|
78
75
|
state.predicateErrorLogged = true;
|
|
79
|
-
|
|
76
|
+
logger.error( `winkComposer/emitIf: predicate threw exception: ${error.message}` );
|
|
80
77
|
}
|
|
81
78
|
state.emissionErrors += 1;
|
|
82
79
|
state.lastEmissionError = error.message;
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
import { executeTriggers } from '../../core/utils/node/index.js';
|
|
42
|
+
import { logger } from '../../core/logger/index.js';
|
|
42
43
|
|
|
43
44
|
const update = function ( state, msg ) {
|
|
44
45
|
if ( state.disable || state.pause ) return state;
|
|
@@ -64,7 +65,7 @@ const update = function ( state, msg ) {
|
|
|
64
65
|
} catch ( error ) {
|
|
65
66
|
if ( !state.tunableErrorLogged ) {
|
|
66
67
|
state.tunableErrorLogged = true;
|
|
67
|
-
|
|
68
|
+
logger.error( `winkComposer/${state.nodeType}: tunable threw: ${error.message}` );
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* is false. Predicate errors are caught and tracked per-episode.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { logger } from '../../core/logger/index.js';
|
|
12
|
+
|
|
11
13
|
const update = function ( state, msg ) {
|
|
12
14
|
if ( state.disable || state.pause ) return state;
|
|
13
15
|
|
|
@@ -26,7 +28,7 @@ const update = function ( state, msg ) {
|
|
|
26
28
|
// Log first error per episode; suppress subsequent until recovery
|
|
27
29
|
if ( !state.predicateErrorLogged ) {
|
|
28
30
|
state.predicateErrorLogged = true;
|
|
29
|
-
|
|
31
|
+
logger.error( `winkComposer/passIf: predicate threw exception: ${error.message}` );
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* emit-if/helpers.js — the two output gates share one failure model.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { logger } from '../../core/logger/index.js';
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* Static fallback error the framework substitutes when an adapter breaks
|
|
13
15
|
* the return contract — no result object at all, or `{ ok: false }` with
|
|
@@ -74,8 +76,8 @@ const sweepAnnotateKeys = function ( state, record, msg ) {
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
if ( invented.length > 0 ) {
|
|
77
|
-
|
|
78
|
-
`
|
|
79
|
+
logger.warn(
|
|
80
|
+
`winkComposer/persistIf: annotate for node '${state.name}' ` +
|
|
79
81
|
`(insightType '${state.insightType}') returned keys that are not declared columns: ` +
|
|
80
82
|
`${invented.join( ', ' )}. These values are never stored, because the persist plan ` +
|
|
81
83
|
'writes only declared columns. Check the key names against the asset class. ' +
|
|
@@ -104,8 +106,62 @@ const recordPersistFailure = function ( state, error ) {
|
|
|
104
106
|
state.writeErrorLogged = true;
|
|
105
107
|
state.firstPersistError = error.message;
|
|
106
108
|
state.firstPersistErrorCode = error.code;
|
|
107
|
-
|
|
109
|
+
logger.error( state.writeErrorLogPrefix + error.code + '): ' + error.message );
|
|
108
110
|
}
|
|
109
111
|
}; // recordPersistFailure()
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Substituted error for a storage adapter that THREW from write() —
|
|
115
|
+
* the other face of a broken return contract (ADR-018: the hot path
|
|
116
|
+
* never throws; a sink answers { ok }). Same code as the
|
|
117
|
+
* malformed-result face: the remediation is identical (fix the
|
|
118
|
+
* non-conforming adapter), and the message names the difference.
|
|
119
|
+
* Allocates on the failure path only. Mirrors emit-if.
|
|
120
|
+
*
|
|
121
|
+
* @param {Error} error - The thrown value
|
|
122
|
+
* @returns {Object} `{ code, message }` for the failure episode
|
|
123
|
+
*/
|
|
124
|
+
const thrownSinkError = function ( error ) {
|
|
125
|
+
return {
|
|
126
|
+
code: 'MALFORMED_RESULT',
|
|
127
|
+
message: `storage write threw instead of returning { ok, error? }: ${error.message}`
|
|
128
|
+
};
|
|
129
|
+
}; // thrownSinkError()
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Writes one record to the wired storage with the sink call fully
|
|
133
|
+
* guarded. A conforming adapter never throws; a throwing one is a
|
|
134
|
+
* broken adapter, recorded in the persist episode — never escaped
|
|
135
|
+
* into the pipeline where it would also cost the message's other
|
|
136
|
+
* outputs. Returns true only on `{ ok: true }`. Mirrors emit-if's
|
|
137
|
+
* deliverToEmitter.
|
|
138
|
+
*
|
|
139
|
+
* @param {Object} state - Node state containing storage, insightType,
|
|
140
|
+
* and partitionId
|
|
141
|
+
* @param {Object} record - Record to persist
|
|
142
|
+
* @returns {boolean} True when the storage accepted the record
|
|
143
|
+
*/
|
|
144
|
+
const writeToStorage = function ( state, record ) {
|
|
145
|
+
try {
|
|
146
|
+
const result = state.storage.write(
|
|
147
|
+
state.insightType,
|
|
148
|
+
record,
|
|
149
|
+
state.partitionId
|
|
150
|
+
);
|
|
151
|
+
if ( result && result.ok ) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
recordPersistFailure( state, ( result && result.error ) || MALFORMED_RESULT_ERROR );
|
|
155
|
+
} catch ( sinkError ) {
|
|
156
|
+
recordPersistFailure( state, thrownSinkError( sinkError ) );
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}; // writeToStorage()
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
assertAnnotateReturn,
|
|
163
|
+
sweepAnnotateKeys,
|
|
164
|
+
recordPersistFailure,
|
|
165
|
+
writeToStorage,
|
|
166
|
+
MALFORMED_RESULT_ERROR
|
|
167
|
+
};
|
|
@@ -90,7 +90,7 @@ const init = function ( spec ) {
|
|
|
90
90
|
state.firstPersistErrorCode = null;
|
|
91
91
|
// Static log prefix pre-built here per ADR-004 (no string building in
|
|
92
92
|
// update beyond the two runtime fields, and only once per episode).
|
|
93
|
-
state.writeErrorLogPrefix = `
|
|
93
|
+
state.writeErrorLogPrefix = `winkComposer/persistIf: storage write failed (node=${state.name}, insightType=${state.insightType}, code=`;
|
|
94
94
|
|
|
95
95
|
return state;
|
|
96
96
|
}; // init()
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* it to the storage buffer if the predicate passed.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { assertAnnotateReturn, sweepAnnotateKeys,
|
|
13
|
+
import { assertAnnotateReturn, sweepAnnotateKeys, writeToStorage } from './helpers.js';
|
|
14
|
+
import { logger } from '../../core/logger/index.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Process incoming message.
|
|
@@ -64,7 +65,7 @@ const update = function ( state, msg ) {
|
|
|
64
65
|
// Log first error per episode; suppress subsequent until recovery
|
|
65
66
|
if ( !state.predicateErrorLogged ) {
|
|
66
67
|
state.predicateErrorLogged = true;
|
|
67
|
-
|
|
68
|
+
logger.error( `winkComposer/persistIf: predicate threw exception: ${error.message}` );
|
|
68
69
|
}
|
|
69
70
|
state.persistErrors += 1;
|
|
70
71
|
state.lastPersistError = error.message;
|
|
@@ -78,31 +79,22 @@ const update = function ( state, msg ) {
|
|
|
78
79
|
|
|
79
80
|
// Write to storage if predicate returned true
|
|
80
81
|
if ( shouldPersist ) {
|
|
81
|
-
// Build record for storage (zero-alloc: storage.write handles it).
|
|
82
82
|
// storage.write() is synchronous per ADR-013; the actual I/O happens
|
|
83
83
|
// asynchronously via the storage adapter's background flush.
|
|
84
84
|
// Per the ADR-018 sink return contract, the storage answers
|
|
85
85
|
// { ok: true } on success or
|
|
86
86
|
// { ok: false, error: { code, message } } on failure.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
if ( result && result.ok ) {
|
|
87
|
+
// writeToStorage guards the call itself too: a THROWING adapter
|
|
88
|
+
// is the other face of a broken contract, recorded in the same
|
|
89
|
+
// adapter episode — a non-conforming adapter can neither fail
|
|
90
|
+
// silently nor throw out of the hot path.
|
|
91
|
+
if ( writeToStorage( state, record ) ) {
|
|
94
92
|
state.persistCount += 1;
|
|
95
93
|
// Wall-clock marker of the last successful write (post-mortem
|
|
96
94
|
// reads only, like the first* error fields).
|
|
97
95
|
state.lastPersistTime = Date.now();
|
|
98
96
|
// Recovery: a successful write closes the failure episode.
|
|
99
97
|
if ( state.writeErrorLogged ) state.writeErrorLogged = false;
|
|
100
|
-
} else {
|
|
101
|
-
// A malformed result (no object at all, or ok:false with no
|
|
102
|
-
// error) counts as a failure with the static fallback error,
|
|
103
|
-
// so a non-conforming adapter can neither fail silently
|
|
104
|
-
// nor throw out of the hot path.
|
|
105
|
-
recordPersistFailure( state, ( result && result.error ) || MALFORMED_RESULT_ERROR );
|
|
106
98
|
}
|
|
107
99
|
}
|
|
108
100
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// nodes/persistence-check/update.js
|
|
2
2
|
|
|
3
3
|
import { executeTriggers } from '../../core/utils/node/index.js';
|
|
4
|
+
import { logger } from '../../core/logger/index.js';
|
|
4
5
|
|
|
5
6
|
const update = function ( state, msg ) {
|
|
6
7
|
if ( state.disable || state.pause ) return state;
|
|
@@ -16,7 +17,7 @@ const update = function ( state, msg ) {
|
|
|
16
17
|
// Log first error per episode; suppress subsequent until recovery
|
|
17
18
|
if ( !state.predicateErrorLogged ) {
|
|
18
19
|
state.predicateErrorLogged = true;
|
|
19
|
-
|
|
20
|
+
logger.error( `winkComposer/persistenceCheck: predicate threw exception: ${error.message}` );
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { logger } from '../../core/logger/index.js';
|
|
2
|
+
|
|
1
3
|
// Helper: Check value against range
|
|
2
4
|
const checkRange = function ( state, value, rangeSpec ) {
|
|
3
5
|
if ( !state.hasRange || !rangeSpec ) {
|
|
@@ -34,7 +36,7 @@ const checkPredicate = function ( state, value, msg ) {
|
|
|
34
36
|
// Log first error per episode; suppress subsequent until recovery
|
|
35
37
|
if ( !state.predicateErrorLogged ) {
|
|
36
38
|
state.predicateErrorLogged = true;
|
|
37
|
-
|
|
39
|
+
logger.error( `winkComposer/sanitize: predicate threw exception: ${error.message}` );
|
|
38
40
|
}
|
|
39
41
|
return false;
|
|
40
42
|
}
|
|
@@ -64,7 +66,7 @@ const update = function ( state, msg ) {
|
|
|
64
66
|
} catch ( error ) {
|
|
65
67
|
if ( !state.tunableErrorLogged ) {
|
|
66
68
|
state.tunableErrorLogged = true;
|
|
67
|
-
|
|
69
|
+
logger.error( `winkComposer/${state.nodeType}: tunable threw: ${error.message}` );
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
}
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
// https://github.com/weinkauf/Persistence1D
|
|
67
67
|
|
|
68
68
|
import { push, isNotFull } from '../../windowing/count-sliding/index.js';
|
|
69
|
+
import { logger } from '../../core/logger/index.js';
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -624,8 +625,8 @@ const resolveThreshold = function ( state, msg ) {
|
|
|
624
625
|
} catch ( error ) {
|
|
625
626
|
if ( !state.tunableErrorLogged ) {
|
|
626
627
|
state.tunableErrorLogged = true;
|
|
627
|
-
const errMsg = '
|
|
628
|
-
|
|
628
|
+
const errMsg = 'winkComposer/' + state.nodeType + ': tunable threw: ' + error.message;
|
|
629
|
+
logger.error( errMsg );
|
|
629
630
|
}
|
|
630
631
|
}
|
|
631
632
|
const epsBase = state.currentThreshold;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// nodes/threshold/update.js
|
|
2
2
|
|
|
3
3
|
import { executeTriggers } from '../../core/utils/node/index.js';
|
|
4
|
+
import { logger } from '../../core/logger/index.js';
|
|
4
5
|
|
|
5
6
|
const update = function ( state, msg ) {
|
|
6
7
|
if ( state.disable || state.pause ) return state;
|
|
@@ -27,7 +28,7 @@ const update = function ( state, msg ) {
|
|
|
27
28
|
// Log first error per episode; suppress subsequent until recovery
|
|
28
29
|
if ( !state.tunableErrorLogged ) {
|
|
29
30
|
state.tunableErrorLogged = true;
|
|
30
|
-
|
|
31
|
+
logger.error( `winkComposer/${state.nodeType}: tunable threw: ${error.message}` );
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -5,7 +5,7 @@ const computeConfidence = function ( state ) {
|
|
|
5
5
|
// Provides gradual trust building as statistics stabilize
|
|
6
6
|
if ( state.samples < state.warmupSamples ) {
|
|
7
7
|
const ramp = state.samples / Math.max( state.warmupSamples, 1 );
|
|
8
|
-
return (
|
|
8
|
+
return Math.min( 1, Math.max( 0, ramp ) );
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const epsilon = state.epsilon ?? 1e-12;
|
|
@@ -45,7 +45,7 @@ const computeConfidence = function ( state ) {
|
|
|
45
45
|
|
|
46
46
|
// Combine factors: far from edge AND low noise AND persistent = high confidence
|
|
47
47
|
const confStable = margin * quietness * persistenceFactor;
|
|
48
|
-
return (
|
|
48
|
+
return Math.min( 1, Math.max( 0, confStable ) );
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// TRENDING CONFIDENCE: "How confident are we in this trend direction?"
|
|
@@ -105,8 +105,8 @@ const computeConfidence = function ( state ) {
|
|
|
105
105
|
// Therefore, we omit the quietness factor for rising/falling states.
|
|
106
106
|
const confTrend = signalClarity * boundaryClarity * persistenceFactor;
|
|
107
107
|
|
|
108
|
-
//
|
|
109
|
-
return (
|
|
108
|
+
// Clamp to [0,1] — the published confidence contract
|
|
109
|
+
return Math.min( 1, Math.max( 0, confTrend ) );
|
|
110
110
|
}; // computeConfidence()
|
|
111
111
|
|
|
112
112
|
export { computeConfidence };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// nodes/trend/update.js
|
|
2
2
|
|
|
3
3
|
import { computeConfidence } from './compute-confidence.js';
|
|
4
|
+
import { logger } from '../../core/logger/index.js';
|
|
4
5
|
|
|
5
6
|
const update = function ( state, msg ) {
|
|
6
7
|
if ( state.disable || state.pause ) return state;
|
|
@@ -47,7 +48,7 @@ const update = function ( state, msg ) {
|
|
|
47
48
|
} catch ( error ) {
|
|
48
49
|
if ( !state.tunableErrorLogged ) {
|
|
49
50
|
state.tunableErrorLogged = true;
|
|
50
|
-
|
|
51
|
+
logger.error( `winkComposer/${state.nodeType}: tunable threw: ${error.message}` );
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
// Extracted helper: set anchor and mark significant.
|
|
23
23
|
// Called from each of the five checks. Zero allocation — scalar
|
|
24
24
|
// assignments only. Reduces update() cyclomatic complexity.
|
|
25
|
+
import { logger } from '../../core/logger/index.js';
|
|
26
|
+
|
|
25
27
|
const setAnchor = function ( state, xVal, slope ) {
|
|
26
28
|
state.anchor = xVal;
|
|
27
29
|
state.anchorSlope = Number.isFinite( slope ) ? slope : 0;
|
|
@@ -80,7 +82,7 @@ const update = function ( state, msg ) {
|
|
|
80
82
|
} catch ( error ) {
|
|
81
83
|
if ( !state.tunableErrorLogged ) {
|
|
82
84
|
state.tunableErrorLogged = true;
|
|
83
|
-
|
|
85
|
+
logger.error( `winkComposer/${state.nodeType}: tunable threw: ${error.message}` );
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
|
|
@@ -38,13 +38,13 @@ const AVAILABLE_STATS = [ 'max', 'sum', 'countAbove' ];
|
|
|
38
38
|
*/
|
|
39
39
|
const group = function ( name, ids, accessor, options ) {
|
|
40
40
|
if ( typeof name !== 'string' || name.length === 0 ) {
|
|
41
|
-
throw new Error( 'features.group: name must be a non-empty string' );
|
|
41
|
+
throw new Error( 'winkComposer/features.group: name must be a non-empty string' );
|
|
42
42
|
}
|
|
43
43
|
if ( !Array.isArray( ids ) || ids.length === 0 ) {
|
|
44
|
-
throw new Error( 'features.group: ids must be a non-empty array' );
|
|
44
|
+
throw new Error( 'winkComposer/features.group: ids must be a non-empty array' );
|
|
45
45
|
}
|
|
46
46
|
if ( typeof accessor !== 'function' ) {
|
|
47
|
-
throw new Error( 'features.group: accessor must be a function' );
|
|
47
|
+
throw new Error( 'winkComposer/features.group: accessor must be a function' );
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const stats = ( options && Array.isArray( options.stats ) ) ?
|
|
@@ -54,7 +54,7 @@ const group = function ( name, ids, accessor, options ) {
|
|
|
54
54
|
for ( let i = 0; i < stats.length; i += 1 ) {
|
|
55
55
|
if ( AVAILABLE_STATS.indexOf( stats[ i ] ) === -1 ) {
|
|
56
56
|
throw new Error(
|
|
57
|
-
'features.group: unknown stat "' + stats[ i ] +
|
|
57
|
+
'winkComposer/features.group: unknown stat "' + stats[ i ] +
|
|
58
58
|
'". Available: ' + AVAILABLE_STATS.join( ', ' )
|
|
59
59
|
);
|
|
60
60
|
}
|
|
@@ -65,7 +65,7 @@ const group = function ( name, ids, accessor, options ) {
|
|
|
65
65
|
1.5;
|
|
66
66
|
|
|
67
67
|
if ( typeof countAboveThreshold !== 'number' ) {
|
|
68
|
-
throw new Error( 'features.group: countAboveThreshold must be a number' );
|
|
68
|
+
throw new Error( 'winkComposer/features.group: countAboveThreshold must be a number' );
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
return {
|
|
@@ -90,10 +90,10 @@ const group = function ( name, ids, accessor, options ) {
|
|
|
90
90
|
*/
|
|
91
91
|
const scalar = function ( name, accessor ) {
|
|
92
92
|
if ( typeof name !== 'string' || name.length === 0 ) {
|
|
93
|
-
throw new Error( 'features.scalar: name must be a non-empty string' );
|
|
93
|
+
throw new Error( 'winkComposer/features.scalar: name must be a non-empty string' );
|
|
94
94
|
}
|
|
95
95
|
if ( typeof accessor !== 'function' ) {
|
|
96
|
-
throw new Error( 'features.scalar: accessor must be a function' );
|
|
96
|
+
throw new Error( 'winkComposer/features.scalar: accessor must be a function' );
|
|
97
97
|
}
|
|
98
98
|
return { type: SCALAR, name: name, accessor: accessor };
|
|
99
99
|
};
|
|
@@ -112,13 +112,13 @@ const scalar = function ( name, accessor ) {
|
|
|
112
112
|
*/
|
|
113
113
|
const counter = function ( name, sourceAccessor, predicate ) {
|
|
114
114
|
if ( typeof name !== 'string' || name.length === 0 ) {
|
|
115
|
-
throw new Error( 'features.counter: name must be a non-empty string' );
|
|
115
|
+
throw new Error( 'winkComposer/features.counter: name must be a non-empty string' );
|
|
116
116
|
}
|
|
117
117
|
if ( typeof sourceAccessor !== 'function' ) {
|
|
118
|
-
throw new Error( 'features.counter: sourceAccessor must be a function' );
|
|
118
|
+
throw new Error( 'winkComposer/features.counter: sourceAccessor must be a function' );
|
|
119
119
|
}
|
|
120
120
|
if ( typeof predicate !== 'function' ) {
|
|
121
|
-
throw new Error( 'features.counter: predicate must be a function' );
|
|
121
|
+
throw new Error( 'winkComposer/features.counter: predicate must be a function' );
|
|
122
122
|
}
|
|
123
123
|
return { type: COUNTER, name: name, sourceAccessor: sourceAccessor, predicate: predicate };
|
|
124
124
|
};
|
|
@@ -134,7 +134,7 @@ const counter = function ( name, sourceAccessor, predicate ) {
|
|
|
134
134
|
*/
|
|
135
135
|
const buildExtractor = function ( schema ) {
|
|
136
136
|
if ( !Array.isArray( schema ) || schema.length === 0 ) {
|
|
137
|
-
throw new Error( 'features.buildExtractor: schema must be a non-empty array' );
|
|
137
|
+
throw new Error( 'winkComposer/features.buildExtractor: schema must be a non-empty array' );
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
// Build feature names from schema
|
|
@@ -149,7 +149,7 @@ const buildExtractor = function ( schema ) {
|
|
|
149
149
|
names.push( col.name );
|
|
150
150
|
} else {
|
|
151
151
|
throw new Error(
|
|
152
|
-
'features.buildExtractor: unknown column type "' +
|
|
152
|
+
'winkComposer/features.buildExtractor: unknown column type "' +
|
|
153
153
|
String( col.type ) + '" at index ' + i
|
|
154
154
|
);
|
|
155
155
|
}
|
|
@@ -160,7 +160,7 @@ const buildExtractor = function ( schema ) {
|
|
|
160
160
|
for ( let i = 0; i < names.length; i += 1 ) {
|
|
161
161
|
if ( seen[ names[ i ] ] ) {
|
|
162
162
|
throw new Error(
|
|
163
|
-
'features.buildExtractor: duplicate feature name "' + names[ i ] + '"'
|
|
163
|
+
'winkComposer/features.buildExtractor: duplicate feature name "' + names[ i ] + '"'
|
|
164
164
|
);
|
|
165
165
|
}
|
|
166
166
|
seen[ names[ i ] ] = true;
|
|
@@ -93,7 +93,7 @@ const train = function ( X, y, options ) {
|
|
|
93
93
|
for ( let i = 0; i < n; i += 1 ) n1 += y[ i ];
|
|
94
94
|
const n0 = n - n1;
|
|
95
95
|
if ( n0 === 0 || n1 === 0 ) {
|
|
96
|
-
throw new Error( 'train: y must contain both classes (0 and 1).' );
|
|
96
|
+
throw new Error( 'winkComposer/train: y must contain both classes (0 and 1).' );
|
|
97
97
|
}
|
|
98
98
|
w[ 0 ] = n / ( 2 * n0 );
|
|
99
99
|
w[ 1 ] = n / ( 2 * n1 );
|
|
@@ -24,7 +24,7 @@ import { validateBinaryArray } from './validate.js';
|
|
|
24
24
|
*/
|
|
25
25
|
const confusionMatrix = function ( predictions, labels ) {
|
|
26
26
|
if ( predictions.length !== labels.length ) {
|
|
27
|
-
throw new Error( 'confusionMatrix: predictions and labels must have the same length.' );
|
|
27
|
+
throw new Error( 'winkComposer/confusionMatrix: predictions and labels must have the same length.' );
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
validateBinaryArray( predictions, 'predictions', 'confusionMatrix' );
|
|
@@ -85,7 +85,7 @@ const classificationMetrics = function ( cm ) {
|
|
|
85
85
|
*/
|
|
86
86
|
const sweepThresholds = function ( probabilities, labels, thresholds ) {
|
|
87
87
|
if ( probabilities.length !== labels.length ) {
|
|
88
|
-
throw new Error( 'sweepThresholds: probabilities and labels must have the same length.' );
|
|
88
|
+
throw new Error( 'winkComposer/sweepThresholds: probabilities and labels must have the same length.' );
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
validateBinaryArray( labels, 'labels', 'sweepThresholds' );
|
|
@@ -177,7 +177,7 @@ const computeRobustParams = function ( X ) {
|
|
|
177
177
|
*/
|
|
178
178
|
const scale = function ( X, params ) {
|
|
179
179
|
if ( !Array.isArray( X ) || X.length === 0 ) {
|
|
180
|
-
throw new Error( 'scale: X must be a non-empty array of rows.' );
|
|
180
|
+
throw new Error( 'winkComposer/scale: X must be a non-empty array of rows.' );
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
const n = X.length;
|
|
@@ -221,7 +221,7 @@ const scale = function ( X, params ) {
|
|
|
221
221
|
result[ i ] = out;
|
|
222
222
|
}
|
|
223
223
|
} else {
|
|
224
|
-
throw new Error( 'scale: unknown method "' + method + '".' );
|
|
224
|
+
throw new Error( 'winkComposer/scale: unknown method "' + method + '".' );
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
return result;
|