@avantf/dsh-mem 0.9.0 → 0.10.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 +42 -43
- package/lib/client.js +103 -34
- package/lib/engine/mem/config/config_files.d.ts +1 -1
- package/lib/engine/mem/db/dao/facts.d.ts +80 -82
- package/lib/engine/mem/dbdrivers/sqlite/facts.d.ts +6 -13
- package/lib/engine/mem/facts_io.d.ts +4 -0
- package/lib/engine/mem/interfaces/repositories.d.ts +5 -19
- package/lib/engine/mem/lifecycle/maintenance.d.ts +8 -4
- package/lib/engine/mem/lifecycle/tick.d.ts +11 -10
- package/lib/engine/mem/lifecycle/trust.d.ts +24 -37
- package/lib/engine/mem/runtime.d.ts +4 -8
- package/lib/engine/mem/store/facts_restore.d.ts +8 -6
- package/lib/engine/mem/store/memory.d.ts +51 -37
- package/lib/engine/mem/store/time_filter.d.ts +1 -1
- package/lib/engine/mem/trustHeartbeat.d.ts +1 -1
- package/lib/engine/mem-contract/config.d.ts +9 -9
- package/lib/engine/mem-contract/index.d.ts +2 -0
- package/lib/engine/mem-contract/time.d.ts +75 -0
- package/lib/engine/mem-contract/tools.d.ts +9 -15
- package/lib/engine/mem-contract/trust_curve.d.ts +119 -0
- package/lib/engine/mem-contract/types.d.ts +62 -12
- package/lib/index.js +738 -557
- package/lib/interface-version.json +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/command.d.ts.map +1 -1
- package/lib/types/envinit-bootstrap.d.ts +1 -1
- package/lib/types/remote.d.ts.map +1 -1
- package/package.json +25 -24
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* `facts` — the memory store's main table.
|
|
3
3
|
*
|
|
4
4
|
* Started with the READ paths the detector, the diagnostics and the clock self-heal need;
|
|
5
|
-
* the write path (`persistFact`, archive/restore
|
|
5
|
+
* the write path (`persistFact`, archive/restore, vector repair) moves here next, so
|
|
6
6
|
* the store ends up holding business rules only and no SQL. See DESIGN §19.
|
|
7
7
|
*/
|
|
8
8
|
import type { Db } from '../port.js';
|
|
9
9
|
import { type TimeRange, type TimeSql } from '../../store/time_filter.js';
|
|
10
10
|
import type { MemoryKind } from '../../../mem-contract/index.js';
|
|
11
|
+
import { type TrustCurve } from '../../../mem-contract/index.js';
|
|
11
12
|
/**
|
|
12
13
|
* Every `facts` column EXCEPT the two BLOBs (`hrr_vector` ~8 KB, `semantic_vector` ~2 KB per row)
|
|
13
14
|
* and the two DEPRECATED provenance columns (`mirror_source` / `mirror_target`, see below).
|
|
@@ -28,7 +29,7 @@ import type { MemoryKind } from '../../../mem-contract/index.js';
|
|
|
28
29
|
* `test/db_lifecycle.spec.ts` asserts this list against `PRAGMA table_info(facts)` MINUS those
|
|
29
30
|
* two deprecations, so a new column fails a test instead of silently vanishing from these reads.
|
|
30
31
|
*/
|
|
31
|
-
export declare const FACT_COLUMNS_NO_BLOB = "fact_id, content, kind, title, tags, trust_score, settle_clock,\n pinned, pinned_at, bonus_count, bonus_window_at, last_reinforced_at, archived_clock,\n retrieval_count, helpful_count, last_retrieved_at, embedding_model, vector_store, status,\n supersedes_id, entities_version, conflict_checked, archived_at, archive_reason, ttl_days,\n valid_from,
|
|
32
|
+
export declare const FACT_COLUMNS_NO_BLOB = "fact_id, content, kind, title, tags, trust_score, settle_clock,\n pinned, pinned_at, bonus_count, bonus_window_at, last_reinforced_at, archived_clock,\n retrieval_count, helpful_count, last_retrieved_at, embedding_model, vector_store, status,\n supersedes_id, entities_version, conflict_checked, archived_at, archive_reason, ttl_days,\n valid_from, valid_from_ms, assert_count, created_at, updated_at";
|
|
32
33
|
export declare const FTS_SEARCH_SQL: string;
|
|
33
34
|
/**
|
|
34
35
|
* {@link FTS_SEARCH_SQL} with the P-08 source predicate, as a SEPARATE statement rather than one
|
|
@@ -191,7 +192,6 @@ export declare class FactsDao {
|
|
|
191
192
|
created_at: string;
|
|
192
193
|
updated_at: string | null;
|
|
193
194
|
valid_from: string | null;
|
|
194
|
-
valid_to: string | null;
|
|
195
195
|
title: string | null;
|
|
196
196
|
}>;
|
|
197
197
|
/** Active rows that carry a persisted vector (index classification). */
|
|
@@ -233,14 +233,16 @@ export declare class FactsDao {
|
|
|
233
233
|
* `createdAt` is named EXPLICITLY rather than left to the column DEFAULT, because a
|
|
234
234
|
* superseding revision must INHERIT the archived row's `created_at` (the memory's first
|
|
235
235
|
* assertion date, distinct from when its text was rewritten). `updated_at` and
|
|
236
|
-
* `last_retrieved_at` are stamped `CURRENT_TIMESTAMP` in the same statement
|
|
237
|
-
*
|
|
238
|
-
*
|
|
236
|
+
* `last_retrieved_at` are stamped `CURRENT_TIMESTAMP` in the same statement, so a revision
|
|
237
|
+
* carries a fresh "last used" observation like the row it replaces (that column no longer
|
|
238
|
+
* feeds any decision — see TRUST_MODEL.md §2.6).
|
|
239
|
+
*
|
|
240
|
+
* `ttl_days` is NOT named: TTL is gone (v2), the column keeps its DEFAULT 0 and nothing
|
|
241
|
+
* reads or writes it any more.
|
|
239
242
|
*/
|
|
240
243
|
insertRevision(v: {
|
|
241
244
|
content: string;
|
|
242
245
|
kind: MemoryKind;
|
|
243
|
-
ttlDays: number;
|
|
244
246
|
supersedesId: number | null;
|
|
245
247
|
hrr: Uint8Array;
|
|
246
248
|
trust: number;
|
|
@@ -256,37 +258,32 @@ export declare class FactsDao {
|
|
|
256
258
|
*/
|
|
257
259
|
title?: string | null;
|
|
258
260
|
/**
|
|
259
|
-
* P-13: the caller's event time
|
|
260
|
-
*
|
|
261
|
+
* P-13: the caller's event time, written straight onto the new row. `null` means "unknown" and
|
|
262
|
+
* is what an omitted argument leaves (the column has no default on purpose).
|
|
261
263
|
* `assert_count` is deliberately NOT named: the column default (1) is the semantics — "this
|
|
262
264
|
* content has been asserted once" — and naming it here would be a second place to keep in step.
|
|
263
265
|
*/
|
|
264
266
|
validFrom?: string | null;
|
|
265
|
-
validTo?: string | null;
|
|
266
267
|
/**
|
|
267
|
-
* The
|
|
268
|
+
* The INSTANT the text bound names (`valid_from_ms`).
|
|
268
269
|
*
|
|
269
270
|
* Written in the same statement as the text, never computed later: the text is what a reader
|
|
270
271
|
* displays and the epoch is what an `event` window compares, so writing them apart would let the
|
|
271
272
|
* two describe different times. `null` = "no event time" (the axis then excludes the row, D5).
|
|
272
273
|
*/
|
|
273
274
|
validFromMs?: number | null;
|
|
274
|
-
validToMs?: number | null;
|
|
275
275
|
}): {
|
|
276
276
|
changes: number;
|
|
277
277
|
lastInsertRowid: number | bigint;
|
|
278
278
|
};
|
|
279
279
|
/**
|
|
280
|
-
* P-10: land an explicitly given
|
|
281
|
-
* caller named `event_date`
|
|
282
|
-
*
|
|
280
|
+
* P-10: land an explicitly given event time on a row that already exists (a duplicate `add`
|
|
281
|
+
* whose caller named `event_date`). An omitted argument keeps what the row has, exactly like
|
|
282
|
+
* `kind` (see `add`'s doc).
|
|
283
283
|
*/
|
|
284
284
|
applyValidity(factId: number, validFrom: {
|
|
285
285
|
text: string;
|
|
286
286
|
ms: number;
|
|
287
|
-
} | undefined, validTo: {
|
|
288
|
-
text: string;
|
|
289
|
-
ms: number;
|
|
290
287
|
} | undefined): void;
|
|
291
288
|
/** P-10: the row's assertion counter (`1` when the column is somehow NULL on a legacy row). */
|
|
292
289
|
assertCount(factId: number): number;
|
|
@@ -299,12 +296,6 @@ export declare class FactsDao {
|
|
|
299
296
|
* two processes cannot interleave between them.
|
|
300
297
|
*/
|
|
301
298
|
incrementAssertCount(factId: number): number;
|
|
302
|
-
/**
|
|
303
|
-
* P-07 audit: stamp `valid_to` on a row that is leaving the active corpus (supersede / a
|
|
304
|
-
* contradiction verdict). `COALESCE` keeps an end the caller already declared (`valid_until`):
|
|
305
|
-
* overwriting it with the archival instant would replace a real answer with an administrative one.
|
|
306
|
-
*/
|
|
307
|
-
markValidTo(factId: number, at: string, atMs?: number | null): void;
|
|
308
299
|
/**
|
|
309
300
|
* P-07: "who superseded this fact", derived from the existing `supersedes_id` link — no
|
|
310
301
|
* redundant column. `MIN` is deterministic when several revisions named the same predecessor
|
|
@@ -418,10 +409,15 @@ export declare class FactsDao {
|
|
|
418
409
|
* reported zero behind. Same rule as {@link setSemanticVector}: what the check SAW changed.
|
|
419
410
|
*/
|
|
420
411
|
requeueConflictCheck(ids: readonly number[]): number;
|
|
421
|
-
/**
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Revive an archived row (R19: clear the archive bookkeeping / refresh `last_retrieved_at`).
|
|
414
|
+
*
|
|
415
|
+
* `ttl_days` is deliberately NOT named any more: an omitted column keeps whatever the row has
|
|
416
|
+
* (the old writing value is preserved as data), and there is no TTL left to inherit.
|
|
417
|
+
*/
|
|
418
|
+
revive(factId: number, kind: MemoryKind, trust: number, clock: number): number;
|
|
419
|
+
/** Land an explicit kind on a row that already exists (pure duplicate). */
|
|
420
|
+
applyValues(factId: number, kind: MemoryKind): void;
|
|
425
421
|
/**
|
|
426
422
|
* Land a title on a row that already exists — the ONE write behind every "backfill" path
|
|
427
423
|
* (JOURNAL_TITLE_SEARCH §11.2/§11.3): a duplicate `add` that names a title for an existing
|
|
@@ -478,22 +474,14 @@ export declare class FactsDao {
|
|
|
478
474
|
archiveSuperseded(factId: number, clock: number): number;
|
|
479
475
|
linkSupersede(factId: number, supersedesId: number): void;
|
|
480
476
|
/**
|
|
481
|
-
* Restore an archived fact, settling its trust and refreshing
|
|
477
|
+
* Restore an archived fact, settling its trust and refreshing `last_retrieved_at` (spec §2.6).
|
|
482
478
|
*
|
|
483
479
|
* `AND status = 'archived'` is load-bearing, like the guard on every sibling above: this UPDATE
|
|
484
480
|
* clears the archive bookkeeping and refreshes `last_retrieved_at`, so ungated it would take an
|
|
485
|
-
* ACTIVE row and
|
|
486
|
-
*
|
|
487
|
-
* returned `1` either way, so the caller could not tell that a row it never asked about had moved.
|
|
481
|
+
* ACTIVE row and reset `trust_score` to the settled value it computed. It returned `1` either
|
|
482
|
+
* way, so the caller could not tell that a row it never asked about had moved.
|
|
488
483
|
*/
|
|
489
484
|
restore(factId: number, trust: number, clock: number): number;
|
|
490
|
-
bumpHelpful(factId: number, delta: number): void;
|
|
491
|
-
/** Feedback outcome: pin at the permanent threshold (trust snaps to 1). */
|
|
492
|
-
pinOutcome(factId: number, clock: number, bonusCount: number, windowAt: string): void;
|
|
493
|
-
/** Feedback outcome: the forget line was reached — archive in the same statement. */
|
|
494
|
-
forgetOutcome(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): void;
|
|
495
|
-
/** Feedback outcome: reinforced (or decayed) but still active. */
|
|
496
|
-
reinforceOutcome(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): void;
|
|
497
485
|
pin(factId: number, clock: number): number;
|
|
498
486
|
unpin(factId: number, clock: number): number;
|
|
499
487
|
/**
|
|
@@ -534,13 +522,24 @@ export declare class FactsDao {
|
|
|
534
522
|
/** ACTIVE and permanent — pinned facts are exempt from decay (the report's `pinned`). */
|
|
535
523
|
countPinnedActive(): number;
|
|
536
524
|
/**
|
|
537
|
-
* ACTIVE facts whose decayed trust
|
|
538
|
-
*
|
|
525
|
+
* ACTIVE, unpinned facts whose decayed trust reaches zero within `withinDays` ACTIVE days.
|
|
526
|
+
*
|
|
527
|
+
* EXACT for every shape, not a linear approximation: the predicate is
|
|
528
|
+
* `trustAfterDays(eff(now), withinDays, curve) <= 0`, which is the same question the tick asks
|
|
529
|
+
* when it archives a row. The caller passes the THRESHOLD (`ceilingForDays(withinDays, curve)`)
|
|
530
|
+
* because "days until zero" is no longer `trust ÷ slope` under `decay_shape: 'accelerating'`;
|
|
531
|
+
* a slope-based SQL predicate under-reported every fact in `(slope × 7, ceilingForDays(7)]`
|
|
532
|
+
* (0.0277 … 0.0515 at the shipped defaults) once accelerating became the default.
|
|
533
|
+
*
|
|
534
|
+
* COST: one row per active unpinned fact, evaluated in JS — the predicate needs `exp`, which the
|
|
535
|
+
* SQL layer cannot express portably. `trust_diagnose` is an operator-only diagnostic and the
|
|
536
|
+
* active set is what `mem_admin list` already walks; if it ever shows up in a profile, prefilter
|
|
537
|
+
* with an index-friendly bound rather than going back to a slope approximation.
|
|
539
538
|
*/
|
|
540
539
|
countForgettingWithin(p: {
|
|
541
|
-
|
|
540
|
+
threshold: number;
|
|
542
541
|
clock: number;
|
|
543
|
-
|
|
542
|
+
curve: TrustCurve;
|
|
544
543
|
}): number;
|
|
545
544
|
/** Facts with a still-live (calendar 24h) reinforcement window — the per-fact quota. */
|
|
546
545
|
/**
|
|
@@ -555,18 +554,15 @@ export declare class FactsDao {
|
|
|
555
554
|
count: number;
|
|
556
555
|
granted: number;
|
|
557
556
|
};
|
|
558
|
-
/** ACTIVE, unpinned facts unused for `days` CALENDAR days (the idle-archival fallback). */
|
|
559
|
-
countIdleCandidates(days: number): number;
|
|
560
557
|
/**
|
|
561
|
-
* Rows still due for
|
|
558
|
+
* Rows still due for ② after a budgeted tick — what the NEXT pass will archive.
|
|
562
559
|
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
560
|
+
* The same predicate the archival statement runs (`FORGET_PREDICATE`), so "backlog" and "what
|
|
561
|
+
* `archiveForgotten` will do next" cannot disagree. Only called when ② actually filled its
|
|
562
|
+
* budget, so the common tick pays nothing for it.
|
|
566
563
|
*/
|
|
567
564
|
countArchiveBacklog(p: {
|
|
568
565
|
forgetThreshold: number;
|
|
569
|
-
idleCalendarDays: number;
|
|
570
566
|
}): number;
|
|
571
567
|
/**
|
|
572
568
|
* Drop unusable (stale-dim) vectors so a re-encode can replace them — and re-queue the same
|
|
@@ -753,35 +749,50 @@ export declare class FactsDao {
|
|
|
753
749
|
* row, and `settle_clock < c` is a seek + range scan of exactly the pending rows.
|
|
754
750
|
*/
|
|
755
751
|
private static readonly SETTLE_PREDICATE;
|
|
756
|
-
/**
|
|
752
|
+
/**
|
|
753
|
+
* The ONE forgetting predicate, shared by the archival statement and its backlog count.
|
|
754
|
+
*
|
|
755
|
+
* Kept as a constant for the same reason as {@link SETTLE_PREDICATE}: the count answers "how many
|
|
756
|
+
* will the next pass archive", so it must be the same WHERE body the archival itself runs —
|
|
757
|
+
* otherwise `archived_deferred` and the actual sweep would drift apart.
|
|
758
|
+
*/
|
|
759
|
+
private static readonly FORGET_PREDICATE;
|
|
760
|
+
/**
|
|
761
|
+
* ① materialize decay, bounded to `budget` rows.
|
|
762
|
+
*
|
|
763
|
+
* PER ROW, through `trustAfterDays` — not one UPDATE with an inline `trust_score − step·Δ`. The
|
|
764
|
+
* formula lives in `contract/src/trust_curve.ts` and nowhere else; the `linear` branch of that
|
|
765
|
+
* function is bit-identical to the statement this replaced (`T − days·slope`, the same two
|
|
766
|
+
* operations in the same order), so P3's equivalent tier materializes exactly the same numbers.
|
|
767
|
+
*
|
|
768
|
+
* The SELECT is the old UPDATE's WHERE body with the old subquery's ORDER/LIMIT, so the row SET is
|
|
769
|
+
* the same one the statement would have touched (and `settled` still counts rows matched, not rows
|
|
770
|
+
* whose value happened to move).
|
|
771
|
+
*
|
|
772
|
+
* COST: one `UPDATE` PER ROW inside the tick's single `IMMEDIATE` transaction (the accelerating
|
|
773
|
+
* formula needs `exp`, which SQL cannot express here, and a second SQL implementation would
|
|
774
|
+
* drift). That is why every caller is budgeted by `trust.tick_max_facts` (5000 by default) and
|
|
775
|
+
* `admin maintenance` is the only unbudgeted pass — measured ~22 ms per 5000 rows, inside the
|
|
776
|
+
* tick's budget; it is a deliberate trade, not a regression.
|
|
777
|
+
*/
|
|
757
778
|
settleBudgeted(p: {
|
|
758
|
-
|
|
779
|
+
curve: TrustCurve;
|
|
759
780
|
clock: number;
|
|
760
781
|
budget: number;
|
|
761
782
|
}): number;
|
|
762
783
|
/** ① materialize decay for every pending row (no budget). */
|
|
763
784
|
settleAll(p: {
|
|
764
|
-
|
|
785
|
+
curve: TrustCurve;
|
|
765
786
|
clock: number;
|
|
766
787
|
}): number;
|
|
788
|
+
/** The shared per-row write of ①: settle the curve at `clock`, then stamp `settle_clock`. */
|
|
789
|
+
private settleRows;
|
|
767
790
|
/** Rows still awaiting ① (the budget ran out). */
|
|
768
791
|
countPendingSettle(p: {
|
|
769
792
|
clock: number;
|
|
770
793
|
}): number;
|
|
771
794
|
/**
|
|
772
|
-
* ②
|
|
773
|
-
*
|
|
774
|
-
* `budget` (0 = none) caps how many rows ONE tick may archive: the tick is a single IMMEDIATE
|
|
775
|
-
* transaction shared by several processes, so an unbudgeted pass over a large backlog held the
|
|
776
|
-
* write lock for as long as it took (measured 14400 rows in one tick). The remainder stays due
|
|
777
|
-
* and is reported as a deferred count, then archived by the next tick.
|
|
778
|
-
*/
|
|
779
|
-
archiveExpiredByTtl(p: {
|
|
780
|
-
clock: number;
|
|
781
|
-
budget?: number;
|
|
782
|
-
}): number[];
|
|
783
|
-
/**
|
|
784
|
-
* ③ trust-driven forgetting (① already clamped trust to the threshold).
|
|
795
|
+
* ② trust-driven forgetting (① already clamped trust to the threshold).
|
|
785
796
|
*
|
|
786
797
|
* Sargable as written (`status`/`pinned` equality + `trust_score <= ?`), and served by
|
|
787
798
|
* `idx_facts_forget(status, pinned, trust_score)`: before that index this statement read every
|
|
@@ -793,20 +804,7 @@ export declare class FactsDao {
|
|
|
793
804
|
budget?: number;
|
|
794
805
|
}): number[];
|
|
795
806
|
/**
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
* The comparison is written as `<expression> < julianday('now', :modifier)` — the same
|
|
799
|
-
* expression `idx_facts_idle_cutoff` indexes — so it is a range seek. The old form
|
|
800
|
-
* (`julianday('now') - <expression> > :days`) called `julianday` on EVERY active row and could
|
|
801
|
-
* not use any index (measured 33–212 ms at 180k).
|
|
802
|
-
*/
|
|
803
|
-
archiveIdle(p: {
|
|
804
|
-
clock: number;
|
|
805
|
-
idleCalendarDays: number;
|
|
806
|
-
budget?: number;
|
|
807
|
-
}): number[];
|
|
808
|
-
/**
|
|
809
|
-
* ⑤ physical delete of archived rows past the retention window.
|
|
807
|
+
* ③ physical delete of archived rows past the retention window.
|
|
810
808
|
*
|
|
811
809
|
* TWO statements in this order, sharing one predicate:
|
|
812
810
|
*
|
|
@@ -815,7 +813,7 @@ export declare class FactsDao {
|
|
|
815
813
|
* DELETE below failed with `SQLITE_CONSTRAINT_FOREIGNKEY` whenever a revision chain
|
|
816
814
|
* crossed `purge_after_archived_days`. Because the whole tick is ONE transaction and it
|
|
817
815
|
* runs from the store's constructor, that failure did not merely skip the purge: it rolled
|
|
818
|
-
* back settle/
|
|
816
|
+
* back settle/forget with it and left the process unable to start, permanently.
|
|
819
817
|
* 2. delete them.
|
|
820
818
|
*
|
|
821
819
|
* The predicate is built once and interpolated into both statements, so "which rows are
|
|
@@ -832,7 +830,7 @@ export declare class FactsDao {
|
|
|
832
830
|
/** The two mutually exclusive purge branches (see `purgeArchived` for why they are split). */
|
|
833
831
|
private static purgeBranches;
|
|
834
832
|
private static purgeParams;
|
|
835
|
-
/** Archived rows still due for
|
|
833
|
+
/** Archived rows still due for ③ after a budgeted pass (0 when the pass finished them). */
|
|
836
834
|
countPurgeBacklog(p: {
|
|
837
835
|
clock: number;
|
|
838
836
|
enabled: number;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @module dbdrivers/sqlite/facts
|
|
10
10
|
*/
|
|
11
|
-
import type { MemoryKind } from '../../../mem-contract/index.js';
|
|
11
|
+
import type { MemoryKind, TrustCurve } from '../../../mem-contract/index.js';
|
|
12
12
|
import { type TimeRange } from '../../store/time_filter.js';
|
|
13
13
|
import type { FactHitMeta, FactRow, FactVectorRow, FactVectorSpaceRow, HrrVectorRow, InsertRevisionOutcome, MemoryRepository } from '../../interfaces/repositories.js';
|
|
14
14
|
import type { FactsDao } from './dao/index.js';
|
|
@@ -31,14 +31,12 @@ export declare class SqliteMemoryRepository implements MemoryRepository {
|
|
|
31
31
|
countInStatus(status: string, kind?: MemoryKind, source?: string): number;
|
|
32
32
|
countActiveWithValidFrom(): number;
|
|
33
33
|
countForgettingWithin(p: {
|
|
34
|
-
|
|
34
|
+
threshold: number;
|
|
35
35
|
clock: number;
|
|
36
|
-
|
|
36
|
+
curve: TrustCurve;
|
|
37
37
|
}): number;
|
|
38
|
-
countIdleCandidates(days: number): number;
|
|
39
38
|
countArchiveBacklog(p: {
|
|
40
39
|
forgetThreshold: number;
|
|
41
|
-
idleCalendarDays: number;
|
|
42
40
|
}): number;
|
|
43
41
|
countPendingConflicts(): number;
|
|
44
42
|
countStaleEntities(version: number): number;
|
|
@@ -86,25 +84,20 @@ export declare class SqliteMemoryRepository implements MemoryRepository {
|
|
|
86
84
|
hrrRowsForFacts(ids: readonly number[], kind?: MemoryKind, source?: string): HrrVectorRow[];
|
|
87
85
|
activeIdsIn(ids: readonly number[], kind?: MemoryKind, source?: string, time?: TimeRange): number[];
|
|
88
86
|
insertRevision(v: Parameters<FactsDao['insertRevision']>[0]): InsertRevisionOutcome;
|
|
89
|
-
applyValidity(factId: number, validFrom: Parameters<FactsDao['applyValidity']>[1]
|
|
90
|
-
applyValues(factId: number, kind: MemoryKind
|
|
87
|
+
applyValidity(factId: number, validFrom: Parameters<FactsDao['applyValidity']>[1]): void;
|
|
88
|
+
applyValues(factId: number, kind: MemoryKind): void;
|
|
91
89
|
setTitle(factId: number, title: string): void;
|
|
92
90
|
insertTitleAlias(factId: number, title: string): void;
|
|
93
91
|
copyTitleAliases(fromFactId: number, toFactId: number, currentTitle: string): void;
|
|
94
92
|
dropTitleAlias(factId: number, title: string): void;
|
|
95
93
|
incrementAssertCount(factId: number): number;
|
|
96
|
-
markValidTo(factId: number, at: string, atMs?: number | null): void;
|
|
97
94
|
linkSupersede(factId: number, supersedesId: number): void;
|
|
98
95
|
archiveSuperseded(factId: number, clock: number): number;
|
|
99
|
-
revive(factId: number, kind: MemoryKind,
|
|
96
|
+
revive(factId: number, kind: MemoryKind, trust: number, clock: number): number;
|
|
100
97
|
archive(factId: number, clock: number, reason: string): number;
|
|
101
98
|
restore(factId: number, trust: number, clock: number): number;
|
|
102
|
-
bumpHelpful(factId: number, delta: number): void;
|
|
103
99
|
pin(factId: number, clock: number): number;
|
|
104
100
|
unpin(factId: number, clock: number): number;
|
|
105
|
-
pinOutcome(factId: number, clock: number, bonusCount: number, windowAt: string): void;
|
|
106
|
-
forgetOutcome(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): void;
|
|
107
|
-
reinforceOutcome(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): void;
|
|
108
101
|
touchUsage(ids: readonly number[]): void;
|
|
109
102
|
settleTrust(factId: number, trust: number, clock: number): void;
|
|
110
103
|
reinforce(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): void;
|
|
@@ -181,6 +181,10 @@ export interface MetadataDropped {
|
|
|
181
181
|
helpful_count: number;
|
|
182
182
|
pinned: number;
|
|
183
183
|
supersedes: number;
|
|
184
|
+
/** v2: the imported row carried a `ttl_days` value; TTL is gone, so it is not written back. */
|
|
185
|
+
ttl_days: number;
|
|
186
|
+
/** v2: the imported row carried a `valid_to` value; the known end is gone. */
|
|
187
|
+
valid_to: number;
|
|
184
188
|
}
|
|
185
189
|
/** The single JSON object `import` prints (`file` is added by the CLI, which knows the argument). */
|
|
186
190
|
export interface ImportReport {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @module interfaces/repositories
|
|
16
16
|
*/
|
|
17
|
-
import type { ContradictionRecord, FactSourceView, MemoryKind, RelationInput } from '../../mem-contract/index.js';
|
|
17
|
+
import type { ContradictionRecord, FactSourceView, MemoryKind, RelationInput, TrustCurve } from '../../mem-contract/index.js';
|
|
18
18
|
import type { GraphReader } from '../../graph-recall/index.js';
|
|
19
19
|
import type { RelationVocabulary, SurfaceMatch } from '../store/relation_planner.js';
|
|
20
20
|
import type { ExtractedEntity } from '../entities/extract.js';
|
|
@@ -36,7 +36,6 @@ export interface FactHitMeta {
|
|
|
36
36
|
created_at: string;
|
|
37
37
|
updated_at: string | null;
|
|
38
38
|
valid_from: string | null;
|
|
39
|
-
valid_to: string | null;
|
|
40
39
|
title: string | null;
|
|
41
40
|
}
|
|
42
41
|
/** One persisted semantic vector, as the reload/repair flows read it. */
|
|
@@ -87,14 +86,12 @@ export interface MemoryRepository {
|
|
|
87
86
|
countInStatus(status: string, kind?: MemoryKind, source?: string): Awaitable<number>;
|
|
88
87
|
countActiveWithValidFrom(): Awaitable<number>;
|
|
89
88
|
countForgettingWithin(p: {
|
|
90
|
-
|
|
89
|
+
threshold: number;
|
|
91
90
|
clock: number;
|
|
92
|
-
|
|
91
|
+
curve: TrustCurve;
|
|
93
92
|
}): Awaitable<number>;
|
|
94
|
-
countIdleCandidates(days: number): Awaitable<number>;
|
|
95
93
|
countArchiveBacklog(p: {
|
|
96
94
|
forgetThreshold: number;
|
|
97
|
-
idleCalendarDays: number;
|
|
98
95
|
}): Awaitable<number>;
|
|
99
96
|
countPendingConflicts(): Awaitable<number>;
|
|
100
97
|
countStaleEntities(version: number): Awaitable<number>;
|
|
@@ -146,7 +143,6 @@ export interface MemoryRepository {
|
|
|
146
143
|
insertRevision(v: {
|
|
147
144
|
content: string;
|
|
148
145
|
kind: MemoryKind;
|
|
149
|
-
ttlDays: number;
|
|
150
146
|
supersedesId: number | null;
|
|
151
147
|
hrr: Uint8Array;
|
|
152
148
|
trust: number;
|
|
@@ -157,18 +153,13 @@ export interface MemoryRepository {
|
|
|
157
153
|
createdAt: string;
|
|
158
154
|
title?: string | null;
|
|
159
155
|
validFrom?: string | null;
|
|
160
|
-
validTo?: string | null;
|
|
161
156
|
validFromMs?: number | null;
|
|
162
|
-
validToMs?: number | null;
|
|
163
157
|
}): Awaitable<InsertRevisionOutcome>;
|
|
164
158
|
applyValidity(factId: number, validFrom: {
|
|
165
159
|
text: string;
|
|
166
160
|
ms: number;
|
|
167
|
-
} | undefined, validTo: {
|
|
168
|
-
text: string;
|
|
169
|
-
ms: number;
|
|
170
161
|
} | undefined): Awaitable<void>;
|
|
171
|
-
applyValues(factId: number, kind: MemoryKind
|
|
162
|
+
applyValues(factId: number, kind: MemoryKind): Awaitable<void>;
|
|
172
163
|
setTitle(factId: number, title: string): Awaitable<void>;
|
|
173
164
|
/** v17: record a handle this fact used to carry, so an old title search still finds it. */
|
|
174
165
|
insertTitleAlias(factId: number, title: string): Awaitable<void>;
|
|
@@ -177,18 +168,13 @@ export interface MemoryRepository {
|
|
|
177
168
|
/** v17: drop an alias that has become the row's current title (a correction back to it). */
|
|
178
169
|
dropTitleAlias(factId: number, title: string): Awaitable<void>;
|
|
179
170
|
incrementAssertCount(factId: number): Awaitable<number>;
|
|
180
|
-
markValidTo(factId: number, at: string, atMs?: number | null): Awaitable<void>;
|
|
181
171
|
linkSupersede(factId: number, supersedesId: number): Awaitable<void>;
|
|
182
172
|
archiveSuperseded(factId: number, clock: number): Awaitable<number>;
|
|
183
|
-
revive(factId: number, kind: MemoryKind,
|
|
173
|
+
revive(factId: number, kind: MemoryKind, trust: number, clock: number): Awaitable<number>;
|
|
184
174
|
archive(factId: number, clock: number, reason: string): Awaitable<number>;
|
|
185
175
|
restore(factId: number, trust: number, clock: number): Awaitable<number>;
|
|
186
|
-
bumpHelpful(factId: number, delta: number): Awaitable<void>;
|
|
187
176
|
pin(factId: number, clock: number): Awaitable<number>;
|
|
188
177
|
unpin(factId: number, clock: number): Awaitable<number>;
|
|
189
|
-
pinOutcome(factId: number, clock: number, bonusCount: number, windowAt: string): Awaitable<void>;
|
|
190
|
-
forgetOutcome(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): Awaitable<void>;
|
|
191
|
-
reinforceOutcome(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): Awaitable<void>;
|
|
192
178
|
touchUsage(ids: readonly number[]): Awaitable<void>;
|
|
193
179
|
settleTrust(factId: number, trust: number, clock: number): Awaitable<void>;
|
|
194
180
|
reinforce(factId: number, trust: number, clock: number, bonusCount: number, windowAt: string): Awaitable<void>;
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Surface stability (D10): the original report keys are all preserved
|
|
6
6
|
* (`decayed / archived_ttl / archived_age / purged / purged_ids / archived_ids`)
|
|
7
|
-
* and the new detail is purely additive. `
|
|
8
|
-
*
|
|
7
|
+
* and the new detail is purely additive. `archived_ttl` / `archived_idle` are kept as
|
|
8
|
+
* CONSTANT 0 and marked `@deprecated`: TTL and idle archival are gone (v2), but a report key
|
|
9
|
+
* is a surface scripts read — deleting it would break them for nothing. `archived_age` now
|
|
10
|
+
* means "the automatic archival that still exists" = `forgot`.
|
|
9
11
|
*
|
|
10
12
|
* The `maintenance` action forces a FULL pass (no settle budget); the automatic
|
|
11
13
|
* startup/heartbeat passes use `tick_max_facts`.
|
|
@@ -15,8 +17,9 @@ import type { Db } from '../db/conn.js';
|
|
|
15
17
|
export interface MaintenanceResult {
|
|
16
18
|
/** Rows whose trust was materialized by the settle sweep (see tick.ts). */
|
|
17
19
|
decayed: number;
|
|
20
|
+
/** @deprecated TTL archival is gone (v2); always 0, kept so existing report readers keep working. */
|
|
18
21
|
archived_ttl: number;
|
|
19
|
-
/**
|
|
22
|
+
/** The automatic archival this pass performed = `forgot` (name kept, D10). */
|
|
20
23
|
archived_age: number;
|
|
21
24
|
purged: number;
|
|
22
25
|
/** Fact ids physically deleted by the purge — callers evict their vectors from the live index. */
|
|
@@ -26,10 +29,11 @@ export interface MaintenanceResult {
|
|
|
26
29
|
settled: number;
|
|
27
30
|
clock: number;
|
|
28
31
|
archived_forgot: number;
|
|
32
|
+
/** @deprecated Idle archival is gone (v2); always 0, kept for the same reason as `archived_ttl`. */
|
|
29
33
|
archived_idle: number;
|
|
30
34
|
/** Rows still pending settle because the budget ran out (always 0 for a forced pass). */
|
|
31
35
|
skipped: number;
|
|
32
|
-
/** Rows still due for
|
|
36
|
+
/** Rows still due for forgot because the budget ran out (0 for a forced pass). */
|
|
33
37
|
archived_deferred: number;
|
|
34
38
|
/** Rows still due for purge when the budget ran out (0 for a forced pass). */
|
|
35
39
|
purged_deferred: number;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One trust/lifecycle tick (spec docs/TRUST_MODEL.md §4) —
|
|
2
|
+
* One trust/lifecycle tick (spec docs/TRUST_MODEL.md §4) — three ordered statements:
|
|
3
3
|
*
|
|
4
4
|
* ① settle — materialize decay for every row whose ACTIVE DAY changed
|
|
5
|
-
* ②
|
|
6
|
-
* ③
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* ② forgot — trust ≤ forget_threshold after settling (pinned exempt)
|
|
6
|
+
* ③ purge — physical delete of archived rows past the window
|
|
7
|
+
*
|
|
8
|
+
* The calendar clocks are GONE (v2): there is no TTL step and no idle fallback, so a fact's
|
|
9
|
+
* lifetime is decided by the active-day curve alone and "expired" is expressed by an explicit
|
|
10
|
+
* `update` / `archive` (docs/TRUST_MODEL.md §3.4). `pinned` is therefore never archived by an
|
|
11
|
+
* automatic pass.
|
|
9
12
|
*
|
|
10
13
|
* EVERY step is budgeted by the same `tick_max_facts`, not just ①.
|
|
11
14
|
*
|
|
12
15
|
* It used to say "only ① is budgeted; ②③④⑤ only ever touch critical rows", and that reading is
|
|
13
|
-
* wrong in the direction that matters:
|
|
16
|
+
* wrong in the direction that matters: every step runs inside the tick's ONE `IMMEDIATE` transaction,
|
|
14
17
|
* which holds the write lock for its whole duration, so an unbounded archive or purge pass over a
|
|
15
18
|
* large corpus blocks every other process (and the startup path, which is where the tick runs) for
|
|
16
19
|
* as long as it takes. Bounding each step is what keeps that lock short; `archived_deferred` and
|
|
@@ -24,17 +27,15 @@ export interface TickResult {
|
|
|
24
27
|
clock: number;
|
|
25
28
|
/** Rows materialized by ① (equals "trust actually changed" — see the note in runTrustTick). */
|
|
26
29
|
settled: number;
|
|
27
|
-
archived_ttl: number;
|
|
28
30
|
archived_forgot: number;
|
|
29
|
-
archived_idle: number;
|
|
30
31
|
purged: number;
|
|
31
32
|
purged_ids: number[];
|
|
32
33
|
archived_ids: number[];
|
|
33
34
|
/** Rows still awaiting ① when the budget ran out (0 for a full pass). */
|
|
34
35
|
skipped: number;
|
|
35
|
-
/** Rows still due for
|
|
36
|
+
/** Rows still due for ② when the budget ran out (0 when the pass finished them). */
|
|
36
37
|
archived_deferred: number;
|
|
37
|
-
/** Rows still due for
|
|
38
|
+
/** Rows still due for ③ when the budget ran out (0 when the pass finished them). */
|
|
38
39
|
purged_deferred: number;
|
|
39
40
|
}
|
|
40
41
|
export declare function runTrustTick(db: Db, cfg: Config, opts: {
|
|
@@ -3,13 +3,22 @@
|
|
|
3
3
|
* (`clock` / `nowMs` are always injected, so every rule is unit-testable).
|
|
4
4
|
*
|
|
5
5
|
* Spec: docs/TRUST_MODEL.md §2. Key rules encoded here:
|
|
6
|
-
* - `eff = pinned ? 1 : clamp(
|
|
6
|
+
* - `eff = pinned ? 1 : clamp(T(clock − settle_clock), 0, 1)` (§2.2), where `T(days)` is the decay
|
|
7
|
+
* curve of `contract/src/trust_curve.ts` — the ONE implementation of the formula
|
|
7
8
|
* - recall only ever RAISES trust (`max(eff, …)`, R2) and never pins (D11)
|
|
8
9
|
* - zero-gain recalls neither consume quota nor stamp `last_reinforced_at` (R10)
|
|
9
|
-
* -
|
|
10
|
+
* - permanent is set ONLY by an explicit `pin`; no write path moves a fact across that line (§2.4)
|
|
10
11
|
* - `enabled=false` degrades `eff()` to the stored value (D12)
|
|
11
12
|
*/
|
|
12
13
|
import type { Config } from '../../mem-contract/index.js';
|
|
14
|
+
import { formatUtcTs, parseUtcTs, type TrustCurve } from '../../mem-contract/index.js';
|
|
15
|
+
/**
|
|
16
|
+
* The UTC stamp parse/render pair is `@avantf/mem-contract/time` — ONE implementation, shared with
|
|
17
|
+
* the two display faces (`formatOffsetIso` for the model, `formatLocalHuman` for the UI), so the
|
|
18
|
+
* "a zone-less stamp means UTC" rule cannot drift between the trust math and what a reader sees.
|
|
19
|
+
* Re-exported here because this module is where the trust code (and its specs) have always found it.
|
|
20
|
+
*/
|
|
21
|
+
export { formatUtcTs, parseUtcTs };
|
|
13
22
|
export declare const DAY_MS = 86400000;
|
|
14
23
|
/** Guard for "did the number actually move" comparisons. */
|
|
15
24
|
export declare const EPSILON = 1e-9;
|
|
@@ -24,20 +33,9 @@ export interface TrustRow {
|
|
|
24
33
|
status: string;
|
|
25
34
|
}
|
|
26
35
|
export declare function clamp01(v: number): number;
|
|
36
|
+
/** The curve of one `trust` section — the single mapping from config keys to the math. */
|
|
37
|
+
export declare function trustCurveOf(cfg: TrustConfig): TrustCurve;
|
|
27
38
|
export declare function isPinned(row: Pick<TrustRow, 'pinned'>): boolean;
|
|
28
|
-
/**
|
|
29
|
-
* `'YYYY-MM-DD HH:MM:SS'` (SQLite UTC) or ISO → epoch ms; null/garbage → null.
|
|
30
|
-
*
|
|
31
|
-
* A stamp with NO zone designator is read as UTC, not as local time. `Date.parse` treats a zone-less
|
|
32
|
-
* ISO string as LOCAL, and everything in this store is written in UTC (`CURRENT_TIMESTAMP`, and
|
|
33
|
-
* {@link formatUtcTs}), so handing one over as-is would shift every quota window and every decay
|
|
34
|
-
* measurement by the host's UTC offset — a fact reinforced at 23:00 UTC+8 would look reinforced 8
|
|
35
|
-
* hours earlier, and the 24h cap would open or close at the wrong moment. An explicit `Z` or
|
|
36
|
-
* `±HH:MM` is left alone.
|
|
37
|
-
*/
|
|
38
|
-
export declare function parseUtcTs(ts: string | null | undefined): number | null;
|
|
39
|
-
/** Epoch ms → `'YYYY-MM-DD HH:MM:SS'` (the SQLite `CURRENT_TIMESTAMP` shape, UTC). */
|
|
40
|
-
export declare function formatUtcTs(ms: number): string;
|
|
41
39
|
/** eff(f) — the trust a caller should act on right now (spec §2.2). */
|
|
42
40
|
export declare function effectiveTrust(row: TrustRow, clock: number, cfg: TrustConfig, enabled?: boolean): number;
|
|
43
41
|
/**
|
|
@@ -48,8 +46,9 @@ export declare function effectiveTrust(row: TrustRow, clock: number, cfg: TrustC
|
|
|
48
46
|
*/
|
|
49
47
|
export declare function displayTrust(row: TrustRow, clock: number, cfg: TrustConfig, enabled?: boolean): number;
|
|
50
48
|
/**
|
|
51
|
-
* Remaining active days:
|
|
52
|
-
* when trust is disabled — the contract's
|
|
49
|
+
* Remaining active days: the inverse of the curve at the current eff (`daysToZero`).
|
|
50
|
+
* `null` for pinned rows, non-active rows and when trust is disabled — the contract's
|
|
51
|
+
* `remaining_days` (spec §6 / R11).
|
|
53
52
|
*/
|
|
54
53
|
export declare function remainingDays(row: TrustRow, clock: number, cfg: TrustConfig, enabled?: boolean): number | null;
|
|
55
54
|
/** Everything a recall write needs to persist (spec §2.3). */
|
|
@@ -66,25 +65,13 @@ export interface RecallOutcome {
|
|
|
66
65
|
* Apply one recall to a fact: settle, then grant a bonus inside the daily quota.
|
|
67
66
|
* Returns `next === settled` when the quota is exhausted, the ceiling is reached,
|
|
68
67
|
* the fact is pinned, or the gain would be zero (R2/R10/D11).
|
|
69
|
-
*/
|
|
70
|
-
export declare function grantRecallBonus(row: TrustRow, clock: number, nowMs: number, cfg: TrustConfig, enabled?: boolean): RecallOutcome;
|
|
71
|
-
/** Everything a feedback write needs to persist (spec §2.4). */
|
|
72
|
-
interface FeedbackOutcome {
|
|
73
|
-
settled: number;
|
|
74
|
-
next: number;
|
|
75
|
-
pin: boolean;
|
|
76
|
-
forget: boolean;
|
|
77
|
-
/** true when the row was left untouched (pinned / archived). */
|
|
78
|
-
untouched: boolean;
|
|
79
|
-
bonusCount: number;
|
|
80
|
-
windowAt: string;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Apply one explicit `helpful` (+1) / `unhelpful` (-1) step.
|
|
84
68
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
69
|
+
* The gain is SEGMENTED at the frozen anchor `BASE_TRUST` (D20):
|
|
70
|
+
* - strictly below the base: `min(BASE_TRUST, T + recall_gain_below_base)`, so the shipped
|
|
71
|
+
* `recall_gain_below_base: 0.1` takes 0.05 → 0.5 in five recalls and never past the base;
|
|
72
|
+
* - AT OR ABOVE the base: `recall_gain_above_base × marginal_decay^count`, capped at the ceiling
|
|
73
|
+
* INVERTED from `max_retention_days` (there is no `recall_ceiling` key any more, D16). The
|
|
74
|
+
* boundary is strictly `<` on purpose: a fact sitting exactly on the base (a fresh one, on the
|
|
75
|
+
* first recall after it has finished settling) takes the above-base branch and starts climbing.
|
|
88
76
|
*/
|
|
89
|
-
export declare function
|
|
90
|
-
export {};
|
|
77
|
+
export declare function grantRecallBonus(row: TrustRow, clock: number, nowMs: number, cfg: TrustConfig, enabled?: boolean): RecallOutcome;
|