oracular 0.12.1 → 0.14.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/CHANGELOG.md +14 -0
- package/README.md +6 -5
- package/dist/index.cjs +14 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -12
- package/dist/index.d.ts +28 -12
- package/dist/index.js +14 -10
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.cts +2 -2
- package/dist/inspect.d.ts +2 -2
- package/dist/presets.d.cts +7 -0
- package/dist/presets.d.ts +7 -0
- package/package.json +8 -2
- package/schema/config.schema.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#71](https://github.com/Yuribenjamin/oracular/pull/71) [`d9e9769`](https://github.com/Yuribenjamin/oracular/commit/d9e9769e922c3bc4251f4585a6cc1c7697468958) Thanks [@Yuribenjamin](https://github.com/Yuribenjamin)! - **A cooldown of its own after the outcome.** The outcome event paused reveals for the reveal `cooldown`: after a "stuck" intent's outcome, help was suppressed for the hour the cooldown named, and the next attempt that went wrong, exactly when the help should be back, got nothing; the only way out, `cooldown: 0`, disabled the reveal cooldown too. `reveal.outcomeCooldown` is the pause after the outcome, resolved per call → intent → engine like the other reveal options, 0 allowed; it defaults to the resolved `cooldown`, so nothing moves unless it is set. The JSON Schema lists it. Found by the second production integration.
|
|
8
|
+
|
|
9
|
+
**Rollback.** A stored config that sets `outcomeCooldown` loads in 0.13 and later, which do not read the stored snapshot when the app passes its config; 0.12 and earlier throw a `TypeError` on the unknown key, so remove it from the saved config before rolling a deploy back that far. The state version is unchanged.
|
|
10
|
+
|
|
11
|
+
## 0.13.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- [#69](https://github.com/Yuribenjamin/oracular/pull/69) [`02e0c44`](https://github.com/Yuribenjamin/oracular/commit/02e0c44be6e1ac167cf5503a38efd0eb38b1dc2d) Thanks [@Yuribenjamin](https://github.com/Yuribenjamin)! - **A rollback no longer trips over a config key it does not know.** `deserialize` validated the config snapshot stored in the state even when the app passed its current config, which replaces it. So a state written by a release that set a new option (`bound` in 0.8, `learning.lift` in 0.9) threw a `TypeError` in the release before it, the changelogs asked for the key to be removed from the saved config before rolling a deploy back, and in `@oracular/react` the load was a `'parse'` failure that blocked writes until `clear()`. With a config passed, the stored snapshot is now not read at all: a key or a value this release does not know is no obstacle, and every number the state holds comes through as before. Without a config, the stored snapshot runs the engine and is read as it was: without the checks that refuse a config that cannot work, and with a key the library does not know still an error, since the engine would otherwise run without an option the state was built under. Nothing changes for a state that loaded before. Under rule 6 of the stability policy, states now move between releases in both directions for the config snapshot as they already did for the state's fields, and the policy says so.
|
|
16
|
+
|
|
3
17
|
## 0.12.1
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -224,7 +224,7 @@ Learned counts never decay unless you set `learning.halfLife`.
|
|
|
224
224
|
|
|
225
225
|
When the outcome event arrives, the intent's evidence returns to the prior (α, β and contributions), while everything learned is kept. The user already upgraded; stop predicting that they will. Set `resetOnOutcome: false` for intents where the outcome can repeat inside one episode.
|
|
226
226
|
|
|
227
|
-
The outcome also starts the
|
|
227
|
+
The outcome also starts a cooldown, `outcomeCooldown`, by default the same `cooldown` a reveal starts: `shouldReveal` stays false until it ends, however strong the fresh evidence, so a user who just bought is not immediately pitched again. Set it on its own, 0 included, for an intent whose outcome recurs within minutes, or a "stuck" intent whose help should be back at the next attempt: the outcome then pauses nothing while a reveal still starts its cooldown. The time of the last outcome is kept on the state as `lastOutcomeAt`.
|
|
228
228
|
|
|
229
229
|
### Every answer comes with its reasons
|
|
230
230
|
|
|
@@ -302,9 +302,10 @@ interface IntentConfig {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
interface RevealOptions {
|
|
305
|
-
threshold?: number;
|
|
306
|
-
z?: number;
|
|
307
|
-
cooldown?: number;
|
|
305
|
+
threshold?: number; // default 0.5
|
|
306
|
+
z?: number; // default 1.28
|
|
307
|
+
cooldown?: number; // after markRevealed; default: the intent's halfLife
|
|
308
|
+
outcomeCooldown?: number; // after the outcome event; default: cooldown. 0 lets the outcome pause nothing
|
|
308
309
|
bound?: 'normal' | 'exact'; // default 'normal': p − z·sd; 'exact': the Beta quantile at Φ(−z)
|
|
309
310
|
}
|
|
310
311
|
```
|
|
@@ -356,7 +357,7 @@ localStorage.setItem('oracular', serialize(state));
|
|
|
356
357
|
const state = deserialize(saved, { config });
|
|
357
358
|
```
|
|
358
359
|
|
|
359
|
-
The new config is validated and swapped in. Intents and signals you no longer declare are dropped (α and β are recomputed from what remains), newly declared intents start at their prior, and everything learned about signals that still exist is kept.
|
|
360
|
+
The new config is validated and swapped in. Intents and signals you no longer declare are dropped (α and β are recomputed from what remains), newly declared intents start at their prior, and everything learned about signals that still exist is kept. The stored snapshot is then not read at all, so a state written by a newer release, with a config key or value this one does not know, still loads after a rollback; loading without a config runs the stored snapshot instead, and a key the library does not know is then an error.
|
|
360
361
|
|
|
361
362
|
States saved by an older version of this library are upgraded on load: `deserialize` migrates the state shapes written by oracular 0.1.0 to 0.11.x (`STATE_VERSION` 1 to 3) to the current one on its own. A `migrate` function is consulted only for a version the library does not know, such as a state written by a newer oracular before a rollback; without one, that throws a `RangeError`:
|
|
362
363
|
|
package/dist/index.cjs
CHANGED
|
@@ -289,7 +289,7 @@ var INTENT_KEYS = [
|
|
|
289
289
|
"attribution",
|
|
290
290
|
"reveal"
|
|
291
291
|
];
|
|
292
|
-
var REVEAL_KEYS = ["threshold", "z", "cooldown", "bound"];
|
|
292
|
+
var REVEAL_KEYS = ["threshold", "z", "cooldown", "outcomeCooldown", "bound"];
|
|
293
293
|
var LEARNING_KEYS = ["halfLife", "gain", "priorEpisodes", "lift"];
|
|
294
294
|
var LIFT_TYPES = [
|
|
295
295
|
"absolute-difference",
|
|
@@ -375,6 +375,9 @@ function normalizeRevealOptions(value, path, strict = true) {
|
|
|
375
375
|
if (record2.cooldown !== void 0) {
|
|
376
376
|
out.cooldown = expectNonNegative(record2.cooldown, `${path}.cooldown`);
|
|
377
377
|
}
|
|
378
|
+
if (record2.outcomeCooldown !== void 0) {
|
|
379
|
+
out.outcomeCooldown = expectNonNegative(record2.outcomeCooldown, `${path}.outcomeCooldown`);
|
|
380
|
+
}
|
|
378
381
|
if (record2.bound !== void 0) {
|
|
379
382
|
if (record2.bound !== "normal" && record2.bound !== "exact") {
|
|
380
383
|
typeError(`${path}.bound`, 'must be "normal" or "exact"');
|
|
@@ -513,10 +516,12 @@ function splitEvaluateOptions(value, path) {
|
|
|
513
516
|
}
|
|
514
517
|
function resolveReveal(config, intent, opts, normalized = false) {
|
|
515
518
|
const o = opts === void 0 ? {} : normalized ? opts : normalizeRevealOptions(opts, "options", false);
|
|
519
|
+
const cooldown = o.cooldown ?? intent.reveal?.cooldown ?? config?.reveal?.cooldown ?? intent.halfLife;
|
|
516
520
|
return {
|
|
517
521
|
threshold: o.threshold ?? intent.reveal?.threshold ?? config?.reveal?.threshold ?? DEFAULT_THRESHOLD,
|
|
518
522
|
z: o.z ?? intent.reveal?.z ?? config?.reveal?.z ?? DEFAULT_Z,
|
|
519
|
-
cooldown
|
|
523
|
+
cooldown,
|
|
524
|
+
outcomeCooldown: o.outcomeCooldown ?? intent.reveal?.outcomeCooldown ?? config?.reveal?.outcomeCooldown ?? cooldown,
|
|
520
525
|
bound: o.bound ?? intent.reveal?.bound ?? config?.reveal?.bound ?? "normal"
|
|
521
526
|
};
|
|
522
527
|
}
|
|
@@ -621,7 +626,7 @@ function buildPrediction(is, name, reveal, at) {
|
|
|
621
626
|
const p = betaMean(is.alpha, is.beta);
|
|
622
627
|
const sd = betaSd(is.alpha, is.beta);
|
|
623
628
|
const revealEnd = cooldownEnd(is.lastRevealedAt, reveal.cooldown, at);
|
|
624
|
-
const outcomeEnd = cooldownEnd(is.lastOutcomeAt, reveal.
|
|
629
|
+
const outcomeEnd = cooldownEnd(is.lastOutcomeAt, reveal.outcomeCooldown, at);
|
|
625
630
|
const suppressedUntil = revealEnd === null ? outcomeEnd : outcomeEnd === null ? revealEnd : Math.max(revealEnd, outcomeEnd);
|
|
626
631
|
return {
|
|
627
632
|
intent: name,
|
|
@@ -1005,9 +1010,9 @@ function deserialize(json, opts = {}) {
|
|
|
1005
1010
|
stored = opts.migrate(parsed, parsed.version);
|
|
1006
1011
|
}
|
|
1007
1012
|
}
|
|
1008
|
-
const
|
|
1009
|
-
const
|
|
1010
|
-
return requireComputable(reconcile(raw, config));
|
|
1013
|
+
const config = opts.config === void 0 ? void 0 : normalizeConfig(opts.config);
|
|
1014
|
+
const raw = parseState(stored, config);
|
|
1015
|
+
return requireComputable(reconcile(raw, raw.config));
|
|
1011
1016
|
}
|
|
1012
1017
|
function typeError2(path, message) {
|
|
1013
1018
|
throw new TypeError(`oracular: ${path} ${message}`);
|
|
@@ -1116,16 +1121,15 @@ function clockState(value, path) {
|
|
|
1116
1121
|
behindAt: nullOrFinite(r.behindAt, `${path}.behindAt`)
|
|
1117
1122
|
};
|
|
1118
1123
|
}
|
|
1119
|
-
function parseState(value) {
|
|
1124
|
+
function parseState(value, config) {
|
|
1120
1125
|
const r = record(value, "state");
|
|
1121
1126
|
if (r.version !== STATE_VERSION) {
|
|
1122
1127
|
throw new RangeError(`oracular: state.version must be ${STATE_VERSION} after migration`);
|
|
1123
1128
|
}
|
|
1129
|
+
const snapshot = record(r.config, "state.config");
|
|
1124
1130
|
return {
|
|
1125
1131
|
version: STATE_VERSION,
|
|
1126
|
-
|
|
1127
|
-
// when it is only stored, and the config passed in replaces it anyway.
|
|
1128
|
-
config: normalizeConfig(r.config, false),
|
|
1132
|
+
config: config ?? normalizeConfig(snapshot, false),
|
|
1129
1133
|
intents: mapOwn(r.intents, "state.intents", intentState),
|
|
1130
1134
|
episode: episodeState(r.episode, "state.episode"),
|
|
1131
1135
|
clock: clockState(r.clock, "state.clock")
|