soulbridge 0.0.100 → 0.0.101

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.
@@ -1,24 +1,24 @@
1
1
  /**
2
2
  * Fact lifecycle vocabulary — declared once, imported everywhere.
3
3
  *
4
- * "superseded" used to be typed out in three files, so renaming it would have left
5
- * one filter silently admitting retired facts. Config modules hold data only; the
6
- * decoding (SQL binding, filters, prompts) lives in the code that imports this.
4
+ * These four replaced none/open/kept/broken/superseded. The older set mixed "what kind of
5
+ * fact" with "how far it has come", so every reader had to remember which values counted as
6
+ * live. The four below are the only ones written now: `raw` is the exchange itself,
7
+ * `candidate` is held but not injected, `active` is in use, `retracted` is kept for the
8
+ * record and read by nothing.
7
9
  *
8
- * Values match the column defaults in sqlite.ts; changing one is a schema migration,
9
- * not a rename.
10
+ * Values match the column defaults in sqlite.ts; changing one is a schema migration, not a
11
+ * rename.
10
12
  */
11
13
  export declare const FACT_STATUS: {
12
- /** Written, no lifecycle decision yet. */
13
- readonly none: 'none';
14
- /** Being worked on (promises, plans). */
15
- readonly open: 'open';
16
- /** Held. */
17
- readonly kept: 'kept';
18
- /** Broken (a promise that did not happen). */
19
- readonly broken: 'broken';
20
- /** Replaced by another fact; kept for the record, never injected. */
21
- readonly superseded: 'superseded';
14
+ /** The exchange itself; not a distilled fact. */
15
+ readonly raw: 'raw';
16
+ /** Held, not injected: unverified inference or an unconfirmed rule. */
17
+ readonly candidate: 'candidate';
18
+ /** In use: retrieval, rules, profile, evidence. */
19
+ readonly active: 'active';
20
+ /** Replaced or withdrawn; kept for the audit trail, injected nowhere. */
21
+ readonly retracted: 'retracted';
22
22
  };
23
23
  export type FactStatus = (typeof FACT_STATUS)[keyof typeof FACT_STATUS];
24
24
  /** Where a fact came from, in the trust sense (not a type). */
@@ -1,24 +1,24 @@
1
1
  /**
2
2
  * Fact lifecycle vocabulary — declared once, imported everywhere.
3
3
  *
4
- * "superseded" used to be typed out in three files, so renaming it would have left
5
- * one filter silently admitting retired facts. Config modules hold data only; the
6
- * decoding (SQL binding, filters, prompts) lives in the code that imports this.
4
+ * These four replaced none/open/kept/broken/superseded. The older set mixed "what kind of
5
+ * fact" with "how far it has come", so every reader had to remember which values counted as
6
+ * live. The four below are the only ones written now: `raw` is the exchange itself,
7
+ * `candidate` is held but not injected, `active` is in use, `retracted` is kept for the
8
+ * record and read by nothing.
7
9
  *
8
- * Values match the column defaults in sqlite.ts; changing one is a schema migration,
9
- * not a rename.
10
+ * Values match the column defaults in sqlite.ts; changing one is a schema migration, not a
11
+ * rename.
10
12
  */
11
13
  export const FACT_STATUS = {
12
- /** Written, no lifecycle decision yet. */
13
- none: 'none',
14
- /** Being worked on (promises, plans). */
15
- open: 'open',
16
- /** Held. */
17
- kept: 'kept',
18
- /** Broken (a promise that did not happen). */
19
- broken: 'broken',
20
- /** Replaced by another fact; kept for the record, never injected. */
21
- superseded: 'superseded',
14
+ /** The exchange itself; not a distilled fact. */
15
+ raw: 'raw',
16
+ /** Held, not injected: unverified inference or an unconfirmed rule. */
17
+ candidate: 'candidate',
18
+ /** In use: retrieval, rules, profile, evidence. */
19
+ active: 'active',
20
+ /** Replaced or withdrawn; kept for the audit trail, injected nowhere. */
21
+ retracted: 'retracted',
22
22
  };
23
23
  /** Where a fact came from, in the trust sense (not a type). */
24
24
  export const FACT_SOURCE = {
@@ -557,7 +557,7 @@ export class Soul {
557
557
  // lost, and "the fact changed" is never claimed without a real predecessor.
558
558
  const target = operation.targetId ? this.store.fact(operation.targetId) : null;
559
559
  const sameScope = target && target.soulId === this.soulId && target.userId === this.userId;
560
- if (target && sameScope && target.status !== 'superseded') {
560
+ if (target && sameScope && isActive(target)) {
561
561
  this.store.supersedeFact(target.id, written.id);
562
562
  }
563
563
  else {
@@ -1,18 +1,17 @@
1
1
  /**
2
2
  * The two decisions every reader used to make for itself, in one place.
3
3
  *
4
- * During the rebuild the `facts` table changes shape in a way that cannot be flipped at once:
5
- * - `role` is a new closed field; old rows have none, and their role lives in the Chinese
6
- * `type_name` label;
7
- * - `status` moves from none/superseded to raw/candidate/active/retracted, but old rows keep
8
- * their old values until the migration runs.
4
+ * `role` says what a fact is for; `status` says how far it has come. Both used to be read by
5
+ * matching Chinese type labels and status strings at each call site, which is how one filter
6
+ * ended up admitting retired facts after a rename.
9
7
  *
10
- * If each call site guessed, they would guess differently — the exact failure mode that made
11
- * "规则/画像/待修" both a type and a state. So: one decoder, used everywhere, written to accept
12
- * both vocabularies until the fallback is deleted in the contract step.
8
+ * This is the contract step: the dual-vocabulary fallback is gone. The migration normalises
9
+ * old rows (none/open/kept → active, broken/superseded → retracted) and the writer always
10
+ * stores a role, so a reader sees exactly one vocabulary.
13
11
  */
14
12
  import type { Fact } from './types.ts';
15
13
  import type { FactRole } from './types.ts';
16
- export declare function roleOf(fact: Pick<Fact, 'role' | 'typeName'>): FactRole;
14
+ /** Rows written before the role column existed cannot appear after the migration. */
15
+ export declare function roleOf(fact: Pick<Fact, 'role'>): FactRole;
17
16
  export declare function isActive(fact: Pick<Fact, 'status'>): boolean;
18
17
  export declare function isRetracted(fact: Pick<Fact, 'status'>): boolean;
@@ -1,17 +1,11 @@
1
- import { FACT_STATE_MIGRATION, LEGACY_ROLE_BY_TYPE } from './config/domains.js';
2
1
  import { FACT_STATUS } from './config/status.js';
3
- const LEGACY_ROLES = LEGACY_ROLE_BY_TYPE;
4
- /** Old and new status values that mean "this is in use". */
5
- const ACTIVE_STATES = new Set(['active', 'none', 'kept', 'open']);
2
+ /** Rows written before the role column existed cannot appear after the migration. */
6
3
  export function roleOf(fact) {
7
- if (fact.role)
8
- return fact.role;
9
- return LEGACY_ROLES[fact.typeName ?? ''] ?? 'observation';
4
+ return fact.role ?? 'observation';
10
5
  }
11
6
  export function isActive(fact) {
12
- return ACTIVE_STATES.has(String(fact.status));
7
+ return String(fact.status) === FACT_STATUS.active;
13
8
  }
14
9
  export function isRetracted(fact) {
15
- const mapped = FACT_STATE_MIGRATION[String(fact.status)] ?? String(fact.status);
16
- return mapped === 'retracted' || String(fact.status) === FACT_STATUS.superseded;
10
+ return String(fact.status) === FACT_STATUS.retracted;
17
11
  }
@@ -1,6 +1,6 @@
1
1
  import { DatabaseSync } from 'node:sqlite';
2
2
  import type { SoulStore } from './store.ts';
3
- export declare const SCHEMA_VERSION = 9;
3
+ export declare const SCHEMA_VERSION = 10;
4
4
  export declare const AFFECT_RETENTION_MS: number;
5
5
  export declare class DbNewerThanBinaryError extends Error {
6
6
  readonly code = "DB_NEWER_THAN_BINARY";
@@ -13,6 +13,7 @@ export declare class DbNewerThanBinaryError extends Error {
13
13
  * old table stays as a read-only legacy copy.
14
14
  */
15
15
  /** v6 → v7: turn threads (a topic's turns need not be adjacent) plus turns.thread_id. */
16
+ export declare function upgradeToV10(db: DatabaseSync): void;
16
17
  export declare function upgradeToV9(db: DatabaseSync): void;
17
18
  export declare function upgradeToV8(db: DatabaseSync): void;
18
19
  export declare function upgradeToV7(db: DatabaseSync): void;
@@ -12,7 +12,7 @@ import { DEFAULT_EMOTION } from './types.js';
12
12
  import { FACT_STATUS, MEMORY_STATUS } from './config/status.js';
13
13
  import { LEGACY_ROLE_BY_TYPE } from './config/domains.js';
14
14
  import { bm25, buildCorpusStats, tokenize } from './scoring.js';
15
- export const SCHEMA_VERSION = 9;
15
+ export const SCHEMA_VERSION = 10;
16
16
  /** How many recent pairs are considered before scoring. Bounded so recall stays O(candidates). */
17
17
  const RECALL_CANDIDATES = 500;
18
18
  export const AFFECT_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
@@ -175,7 +175,7 @@ CREATE TABLE IF NOT EXISTS facts (
175
175
  at_end INTEGER,
176
176
  at_precision TEXT NOT NULL DEFAULT 'unknown',
177
177
  place_id TEXT,
178
- status TEXT NOT NULL DEFAULT 'none',
178
+ status TEXT NOT NULL DEFAULT 'active',
179
179
  confidence REAL NOT NULL DEFAULT 1,
180
180
  source TEXT NOT NULL DEFAULT 'experienced',
181
181
  source_turn_id TEXT,
@@ -299,6 +299,12 @@ function upgradeToV3(db) {
299
299
  * old table stays as a read-only legacy copy.
300
300
  */
301
301
  /** v6 → v7: turn threads (a topic's turns need not be adjacent) plus turns.thread_id. */
302
+ export function upgradeToV10(db) {
303
+ // The four-state vocabulary (config/status.ts). Everything written under the old names
304
+ // becomes one of the two states the code now reads; after this the fallbacks are gone.
305
+ db.exec("UPDATE facts SET status = 'active' WHERE status IN ('none','open','kept')");
306
+ db.exec("UPDATE facts SET status = 'retracted' WHERE status IN ('broken','superseded')");
307
+ }
302
308
  export function upgradeToV9(db) {
303
309
  // Time became locatability (ADR 0032): a fact may be known to exist without a
304
310
  // date, and that is a value — unknown — not a reason to discard it.
@@ -442,8 +448,8 @@ export function openSqlite(file) {
442
448
  upgradeToV7(db);
443
449
  if (version < 8)
444
450
  upgradeToV8(db);
445
- if (version < 9)
446
- upgradeToV9(db);
451
+ if (version < 10)
452
+ upgradeToV10(db);
447
453
  // v6 adds tables only; SCHEMA creates them on every open, nothing to copy.
448
454
  setVersion(db, SCHEMA_VERSION);
449
455
  db.exec('COMMIT');
@@ -827,7 +833,7 @@ export function openSqlite(file) {
827
833
  },
828
834
  supersedeFact(oldId, newId) {
829
835
  withRetry(() => db
830
- .prepare(`UPDATE facts SET status = '${FACT_STATUS.superseded}', superseded_by = ? WHERE id = ?`)
836
+ .prepare(`UPDATE facts SET status = '${FACT_STATUS.retracted}', superseded_by = ? WHERE id = ?`)
831
837
  .run(newId, oldId));
832
838
  },
833
839
  /* ---------------- gaps ---------------- */
@@ -1083,7 +1089,7 @@ export function openSqlite(file) {
1083
1089
  .prepare(`SELECT DISTINCT f.* FROM facts f
1084
1090
  JOIN fact_slots s ON s.fact_id = f.id AND s.value_type = 'entity'
1085
1091
  JOIN entities e ON e.id = s.entity_id
1086
- WHERE f.soul_id = ? AND f.user_id = ? AND f.status != '${FACT_STATUS.superseded}'
1092
+ WHERE f.soul_id = ? AND f.user_id = ? AND f.status = '${FACT_STATUS.active}'
1087
1093
  AND (e.name = ? OR e.aliases LIKE ?)
1088
1094
  AND (? IS NULL OR f.statement LIKE ? OR COALESCE(f.type_name, '') LIKE ?)
1089
1095
  ORDER BY COALESCE(f.at, f.created_at) DESC
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soulbridge",
3
- "version": "0.0.100",
3
+ "version": "0.0.101",
4
4
  "description": "A personality layer for any agent: continuity, forgetting, and growth across tools.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",