oracular 0.3.0 → 0.4.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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#22](https://github.com/Yuribenjamin/oracular/pull/22) [`0bb5841`](https://github.com/Yuribenjamin/oracular/commit/0bb5841fb93d2f16dcac74601f1ee218b70dda0e) Thanks [@Yuribenjamin](https://github.com/Yuribenjamin)! - Three small ergonomics for writing configs.
8
+
9
+ - **`validateConfig(config)`** runs the checks `createState` and `deserialize` run, without building a state, and returns the normalized copy they would store. It accepts `unknown`, so parsed JSON can be passed as it is; use it to fail fast at startup or in a test.
10
+ - **`ms`**: `ms.seconds(n)`, `ms.minutes(n)`, `ms.hours(n)`, `ms.days(n)` and `ms.weeks(n)` spell durations in milliseconds, so `halfLife: ms.days(7)` replaces `7 * 24 * 60 * 60 * 1000`. Plain multiplications, nothing else.
11
+ - **A JSON Schema for the config**, shipped in the package as `oracular/config.schema.json` and served at https://yuribenjamin.github.io/oracular/config.schema.json, for completion and checks on a config kept as JSON. `oracular replay --config file.json` now ignores a top-level `$schema` key, so a file can point at the schema and still be the config the engine runs.
12
+
3
13
  ## 0.3.0
4
14
 
5
15
  ### Minor Changes
package/README.md CHANGED
@@ -250,6 +250,8 @@ createRecord(state, event, { installId, mode }): EventRecord // one recorder l
250
250
  createEndRecord(at, { installId, mode }): EndRecord // one recorder line: the episode ended
251
251
  serialize(state): string
252
252
  deserialize(json: string, opts?: { config?: EngineConfig; migrate?: Migrate }): EngineState
253
+ validateConfig(config: unknown): EngineConfig // the checks createState runs, without a state; returns the normalized copy
254
+ ms.seconds(n), ms.minutes(n), ms.hours(n), ms.days(n), ms.weeks(n) // durations in milliseconds
253
255
  STATE_VERSION // 2
254
256
  RECORD_VERSION // 1
255
257
  ```
@@ -286,7 +288,7 @@ interface RevealOptions {
286
288
  }
287
289
  ```
288
290
 
289
- Reveal options resolve per call → intent → engine → defaults. Invalid config throws a `TypeError` or `RangeError` with the offending path in the message.
291
+ Reveal options resolve per call → intent → engine → defaults. Invalid config throws a `TypeError` or `RangeError` with the offending path in the message. `validateConfig(config)` runs those checks without building a state and returns the normalized copy `createState` stores, so a bad weight fails at startup or in a test rather than on first use. For a config kept as JSON, the package ships a [JSON Schema](./schema/config.schema.json) as `oracular/config.schema.json`, also served at <https://yuribenjamin.github.io/oracular/config.schema.json>, for editor completion; `oracular replay` ignores the `$schema` key such a file carries, the engine does not.
290
292
 
291
293
  ### Results
292
294
 
@@ -317,7 +319,7 @@ interface SignalExplanation {
317
319
 
318
320
  ### Time
319
321
 
320
- `at` is a timestamp. Every duration (`halfLife`, `cooldown`, `episodeTimeout`, `learning.halfLife`) is in the same unit as `at`. Milliseconds from `Date.now()` are the obvious choice, but the library never calls `Date.now()` itself: you pass time in, which keeps every function deterministic and makes tests trivial. A clock that jumps backwards is tolerated (no decay, no error).
322
+ `at` is a timestamp. Every duration (`halfLife`, `cooldown`, `episodeTimeout`, `learning.halfLife`) is in the same unit as `at`. Milliseconds from `Date.now()` are the obvious choice, but the library never calls `Date.now()` itself: you pass time in, which keeps every function deterministic and makes tests trivial. A clock that jumps backwards is tolerated (no decay, no error). When `at` is in milliseconds, `ms.days(7)`, `ms.hours(3)` and `ms.minutes(30)` spell the durations; they are plain multiplications.
321
323
 
322
324
  ## Persistence
323
325
 
@@ -385,7 +387,7 @@ npx oracular replay shadow.ndjson --config oracular.config.json
385
387
 
386
388
  ## Roadmap
387
389
 
388
- Next is 0.3, themed **proof**: a recorder format for shadow mode, `npx oracular replay` to measure a config offline, per-episode saturation, and state robustness tests. Then cross-episode attribution and `tune` in 0.4, and a written stability policy before 1.0. Framework bindings wait for a third integration. The full list, with the reasoning and what was declined, is on the [roadmap page](https://yuribenjamin.github.io/oracular/guide/roadmap).
390
+ Now is 0.4, themed **learning that matches how people behave**: cross-episode attribution, so a session's signals can earn credit for an outcome days later; `npx oracular tune` to seed a config from a shadow log; presets; and small ergonomics (`validateConfig`, `ms`, a JSON Schema for the config). 0.3 shipped shadow mode, `npx oracular replay` and per-episode saturation. Then a written stability policy before 1.0. Framework bindings wait for a third integration. The full list, with the reasoning and what was declined, is on the [roadmap page](https://yuribenjamin.github.io/oracular/guide/roadmap).
389
391
 
390
392
  ## Contributing
391
393
 
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { readFileSync } from "fs";
7
7
  import { createRequire } from "module";
8
8
  import { extname, resolve } from "path";
9
9
  import { pathToFileURL } from "url";
10
- import { createState as createState2 } from "oracular";
10
+ import { validateConfig } from "oracular";
11
11
 
12
12
  // cli/log.ts
13
13
  import { RECORD_VERSION } from "oracular";
@@ -425,7 +425,8 @@ reveals per user, and a reliability table with a Brier score.
425
425
 
426
426
  Options:
427
427
  --config <file> the config to replay under: a .json file, or a .js, .mjs or .cjs
428
- module whose default (or \`config\`) export is the config
428
+ module whose default (or \`config\`) export is the config. A top-level
429
+ "$schema" key in a .json file is ignored
429
430
  --thresholds <list> thresholds to score, comma separated
430
431
  (default: 0.3 to 0.9 by 0.1, plus each intent's configured threshold)
431
432
  --window <duration> how long after a reveal an outcome still counts for it: 3d, 12h, 90m
@@ -544,6 +545,9 @@ async function loadConfig(path, io) {
544
545
  } catch (error) {
545
546
  throw new Error(`${path}: ${error.message}`);
546
547
  }
548
+ if (typeof value === "object" && value !== null && "$schema" in value) {
549
+ value = Object.fromEntries(Object.entries(value).filter(([key]) => key !== "$schema"));
550
+ }
547
551
  } else if (ext === ".js" || ext === ".mjs" || ext === ".cjs") {
548
552
  const mod = await import(pathToFileURL(path).href);
549
553
  value = mod.default ?? mod.config;
@@ -568,7 +572,7 @@ async function replayCommand(argv, io) {
568
572
  const config = await loadConfig(configPath, io);
569
573
  let declared;
570
574
  try {
571
- declared = Object.keys(createState2(config).config.intents);
575
+ declared = Object.keys(validateConfig(config).intents);
572
576
  } catch (error) {
573
577
  throw new Error(
574
578
  `${configPath} is not a valid config: ${error.message.replace(/^oracular: /, "")}`
package/dist/index.cjs CHANGED
@@ -943,6 +943,20 @@ function reconcile(raw, config) {
943
943
  };
944
944
  }
945
945
 
946
+ // src/units.ts
947
+ var ms = {
948
+ seconds: (n) => n * 1e3,
949
+ minutes: (n) => n * 6e4,
950
+ hours: (n) => n * 36e5,
951
+ days: (n) => n * 864e5,
952
+ weeks: (n) => n * 6048e5
953
+ };
954
+
955
+ // src/validate.ts
956
+ function validateConfig(config) {
957
+ return normalizeConfig(config);
958
+ }
959
+
946
960
  exports.RECORD_VERSION = RECORD_VERSION;
947
961
  exports.STATE_VERSION = STATE_VERSION;
948
962
  exports.createEndRecord = createEndRecord;
@@ -955,9 +969,11 @@ exports.evaluate = evaluate;
955
969
  exports.evidenceToReveal = evidenceToReveal;
956
970
  exports.explain = explain;
957
971
  exports.markRevealed = markRevealed;
972
+ exports.ms = ms;
958
973
  exports.observe = observe;
959
974
  exports.predict = predict;
960
975
  exports.serialize = serialize;
961
976
  exports.shouldReveal = shouldReveal;
977
+ exports.validateConfig = validateConfig;
962
978
  //# sourceMappingURL=index.cjs.map
963
979
  //# sourceMappingURL=index.cjs.map