@ai-agent-forge/plugin-memory 0.85.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 +65 -0
- package/agent-forge.json +11 -0
- package/dist/capability.d.ts +182 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +2565 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +36 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +154 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/assistant-card.d.ts +31 -0
- package/dist/memory/assistant-card.d.ts.map +1 -0
- package/dist/memory/assistant-card.js +108 -0
- package/dist/memory/assistant-card.js.map +1 -0
- package/dist/memory/candidates.d.ts +65 -0
- package/dist/memory/candidates.d.ts.map +1 -0
- package/dist/memory/candidates.js +100 -0
- package/dist/memory/candidates.js.map +1 -0
- package/dist/memory/code-memory.d.ts +89 -0
- package/dist/memory/code-memory.d.ts.map +1 -0
- package/dist/memory/code-memory.js +104 -0
- package/dist/memory/code-memory.js.map +1 -0
- package/dist/memory/compaction-sequencer.d.ts +63 -0
- package/dist/memory/compaction-sequencer.d.ts.map +1 -0
- package/dist/memory/compaction-sequencer.js +129 -0
- package/dist/memory/compaction-sequencer.js.map +1 -0
- package/dist/memory/continuation.d.ts +44 -0
- package/dist/memory/continuation.d.ts.map +1 -0
- package/dist/memory/continuation.js +49 -0
- package/dist/memory/continuation.js.map +1 -0
- package/dist/memory/curation.d.ts +58 -0
- package/dist/memory/curation.d.ts.map +1 -0
- package/dist/memory/curation.js +68 -0
- package/dist/memory/curation.js.map +1 -0
- package/dist/memory/egress-policy.d.ts +50 -0
- package/dist/memory/egress-policy.d.ts.map +1 -0
- package/dist/memory/egress-policy.js +71 -0
- package/dist/memory/egress-policy.js.map +1 -0
- package/dist/memory/embedding-provider.d.ts +70 -0
- package/dist/memory/embedding-provider.d.ts.map +1 -0
- package/dist/memory/embedding-provider.js +164 -0
- package/dist/memory/embedding-provider.js.map +1 -0
- package/dist/memory/embedding-reranker.d.ts +56 -0
- package/dist/memory/embedding-reranker.d.ts.map +1 -0
- package/dist/memory/embedding-reranker.js +109 -0
- package/dist/memory/embedding-reranker.js.map +1 -0
- package/dist/memory/foundation.d.ts +168 -0
- package/dist/memory/foundation.d.ts.map +1 -0
- package/dist/memory/foundation.js +487 -0
- package/dist/memory/foundation.js.map +1 -0
- package/dist/memory/host-module-import.d.ts +25 -0
- package/dist/memory/host-module-import.d.ts.map +1 -0
- package/dist/memory/host-module-import.js +41 -0
- package/dist/memory/host-module-import.js.map +1 -0
- package/dist/memory/ledger.d.ts +58 -0
- package/dist/memory/ledger.d.ts.map +1 -0
- package/dist/memory/ledger.js +315 -0
- package/dist/memory/ledger.js.map +1 -0
- package/dist/memory/lifecycle.d.ts +124 -0
- package/dist/memory/lifecycle.d.ts.map +1 -0
- package/dist/memory/lifecycle.js +201 -0
- package/dist/memory/lifecycle.js.map +1 -0
- package/dist/memory/memory-network.d.ts +55 -0
- package/dist/memory/memory-network.d.ts.map +1 -0
- package/dist/memory/memory-network.js +70 -0
- package/dist/memory/memory-network.js.map +1 -0
- package/dist/memory/model-cache-hygiene.d.ts +18 -0
- package/dist/memory/model-cache-hygiene.d.ts.map +1 -0
- package/dist/memory/model-cache-hygiene.js +38 -0
- package/dist/memory/model-cache-hygiene.js.map +1 -0
- package/dist/memory/preference-disambiguator.d.ts +43 -0
- package/dist/memory/preference-disambiguator.d.ts.map +1 -0
- package/dist/memory/preference-disambiguator.js +81 -0
- package/dist/memory/preference-disambiguator.js.map +1 -0
- package/dist/memory/preference-lifecycle.d.ts +66 -0
- package/dist/memory/preference-lifecycle.d.ts.map +1 -0
- package/dist/memory/preference-lifecycle.js +129 -0
- package/dist/memory/preference-lifecycle.js.map +1 -0
- package/dist/memory/preference-promotion.d.ts +87 -0
- package/dist/memory/preference-promotion.d.ts.map +1 -0
- package/dist/memory/preference-promotion.js +102 -0
- package/dist/memory/preference-promotion.js.map +1 -0
- package/dist/memory/preference-resolver.d.ts +44 -0
- package/dist/memory/preference-resolver.d.ts.map +1 -0
- package/dist/memory/preference-resolver.js +107 -0
- package/dist/memory/preference-resolver.js.map +1 -0
- package/dist/memory/purge-journal.d.ts +76 -0
- package/dist/memory/purge-journal.d.ts.map +1 -0
- package/dist/memory/purge-journal.js +130 -0
- package/dist/memory/purge-journal.js.map +1 -0
- package/dist/memory/purge.d.ts +90 -0
- package/dist/memory/purge.d.ts.map +1 -0
- package/dist/memory/purge.js +138 -0
- package/dist/memory/purge.js.map +1 -0
- package/dist/memory/recall-agent.d.ts +84 -0
- package/dist/memory/recall-agent.d.ts.map +1 -0
- package/dist/memory/recall-agent.js +199 -0
- package/dist/memory/recall-agent.js.map +1 -0
- package/dist/memory/recall-index.d.ts +87 -0
- package/dist/memory/recall-index.d.ts.map +1 -0
- package/dist/memory/recall-index.js +222 -0
- package/dist/memory/recall-index.js.map +1 -0
- package/dist/memory/recall-packet.d.ts +121 -0
- package/dist/memory/recall-packet.d.ts.map +1 -0
- package/dist/memory/recall-packet.js +156 -0
- package/dist/memory/recall-packet.js.map +1 -0
- package/dist/memory/scheduler-api.d.ts +99 -0
- package/dist/memory/scheduler-api.d.ts.map +1 -0
- package/dist/memory/scheduler-api.js +93 -0
- package/dist/memory/scheduler-api.js.map +1 -0
- package/dist/memory/scheduler.d.ts +55 -0
- package/dist/memory/scheduler.d.ts.map +1 -0
- package/dist/memory/scheduler.js +91 -0
- package/dist/memory/scheduler.js.map +1 -0
- package/dist/memory/store.d.ts +107 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +208 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/suite-memory.d.ts +208 -0
- package/dist/memory/suite-memory.d.ts.map +1 -0
- package/dist/memory/suite-memory.js +288 -0
- package/dist/memory/suite-memory.js.map +1 -0
- package/dist/memory/transfer.d.ts +142 -0
- package/dist/memory/transfer.d.ts.map +1 -0
- package/dist/memory/transfer.js +210 -0
- package/dist/memory/transfer.js.map +1 -0
- package/dist/memory/vector-index.d.ts +39 -0
- package/dist/memory/vector-index.d.ts.map +1 -0
- package/dist/memory/vector-index.js +136 -0
- package/dist/memory/vector-index.js.map +1 -0
- package/dist/memory/write-budget.d.ts +33 -0
- package/dist/memory/write-budget.d.ts.map +1 -0
- package/dist/memory/write-budget.js +45 -0
- package/dist/memory/write-budget.js.map +1 -0
- package/dist/testing/memory-testkit.d.ts +149 -0
- package/dist/testing/memory-testkit.d.ts.map +1 -0
- package/dist/testing/memory-testkit.js +438 -0
- package/dist/testing/memory-testkit.js.map +1 -0
- package/dist/utils/sync-sleep.d.ts +2 -0
- package/dist/utils/sync-sleep.d.ts.map +1 -0
- package/dist/utils/sync-sleep.js +11 -0
- package/dist/utils/sync-sleep.js.map +1 -0
- package/package.json +56 -0
- package/plugin.json +10 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Foundation — three-scope store with revisions, TTL, and retention
|
|
3
|
+
* inputs (1C.1b).
|
|
4
|
+
*
|
|
5
|
+
* Scope decides which partition a committed atom belongs to (session / cycle /
|
|
6
|
+
* long-term); retention decides how long it stays recallable. Retention mode
|
|
7
|
+
* IDs are opaque strings resolved through a versioned registry provided by the
|
|
8
|
+
* Profile — the Foundation never hardcodes business durations. Expired records
|
|
9
|
+
* lose recall eligibility but are NOT physically purged here (physical purge
|
|
10
|
+
* is the controlled 1C.6d gate).
|
|
11
|
+
*/
|
|
12
|
+
import type { JsonValue } from "@agent-forge/plugin-sdk";
|
|
13
|
+
import type { MemoryAtomV1, MemoryScopeV1, MemorySuiteFilterStatsV1 } from "./foundation.ts";
|
|
14
|
+
/** One retention mode published by a Profile's retention-mode registry. */
|
|
15
|
+
export interface RetentionModeDefinitionV1 {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
/** Recall duration in ms. Absent = no expiry (e.g. `permanent`). */
|
|
18
|
+
readonly durationMs?: number;
|
|
19
|
+
readonly autoPurge: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Versioned retention-mode registry snapshot. */
|
|
22
|
+
export interface RetentionModeRegistryV1 {
|
|
23
|
+
readonly policyVersion: string;
|
|
24
|
+
readonly modes: readonly RetentionModeDefinitionV1[];
|
|
25
|
+
get(id: string): RetentionModeDefinitionV1 | undefined;
|
|
26
|
+
}
|
|
27
|
+
/** Validates a retention-mode registry: parseable, finite, non-negative, unique, traceable. */
|
|
28
|
+
export declare function validateRetentionModeRegistry(value: unknown): RetentionModeRegistryV1;
|
|
29
|
+
/** First-party default registry (short/standard/long/permanent). Durations live here, not in Foundation logic. */
|
|
30
|
+
export declare function createFirstPartyRetentionRegistry(): RetentionModeRegistryV1;
|
|
31
|
+
export interface MemoryStoreCommitOptions {
|
|
32
|
+
/** Overrides the atom's retentionMode id at commit time (policy-selected). */
|
|
33
|
+
readonly retentionModeId?: string;
|
|
34
|
+
}
|
|
35
|
+
/** A committed atom plus the store-assigned bookkeeping facts. */
|
|
36
|
+
export interface CommittedMemoryV1<TPayload extends JsonValue = JsonValue> {
|
|
37
|
+
readonly atom: MemoryAtomV1<TPayload>;
|
|
38
|
+
/** Monotonic across the whole store. */
|
|
39
|
+
readonly storeRevision: number;
|
|
40
|
+
/** Monotonic within the atom's scope partition. */
|
|
41
|
+
readonly scopeRevision: number;
|
|
42
|
+
readonly committedAt: number;
|
|
43
|
+
/** Recall deadline computed from the retention registry. Absent = no expiry. */
|
|
44
|
+
readonly purgeAt?: number;
|
|
45
|
+
readonly retentionModeId: string;
|
|
46
|
+
}
|
|
47
|
+
export interface MemoryStoreQuery {
|
|
48
|
+
readonly owner: string;
|
|
49
|
+
/**
|
|
50
|
+
* Suite-scoped read boundary (方案系统设计 §6.1/§11). When set, only atoms
|
|
51
|
+
* whose suiteId equals this value — plus explicitly promoted user-default
|
|
52
|
+
* preferences — are visible; legacy (suiteId-less) atoms are skipped
|
|
53
|
+
* fail-safe. When absent, no suite filtering happens (suite-unscoped read).
|
|
54
|
+
*/
|
|
55
|
+
readonly suiteId?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface MemoryStoreStats {
|
|
58
|
+
readonly active: number;
|
|
59
|
+
readonly expired: number;
|
|
60
|
+
readonly byScope: Readonly<Record<MemoryScopeV1, {
|
|
61
|
+
active: number;
|
|
62
|
+
expired: number;
|
|
63
|
+
}>>;
|
|
64
|
+
}
|
|
65
|
+
/** Result of {@link MemoryStoreV1.listForSuite}: visible records plus skip counters. */
|
|
66
|
+
export interface MemoryStoreSuitePageV1 {
|
|
67
|
+
readonly records: readonly CommittedMemoryV1[];
|
|
68
|
+
/** Excluded-record counters for the suite read boundary (设计 §11). */
|
|
69
|
+
readonly filter: MemorySuiteFilterStatsV1;
|
|
70
|
+
}
|
|
71
|
+
export interface MemoryStoreV1 {
|
|
72
|
+
commit(atom: MemoryAtomV1, options?: MemoryStoreCommitOptions): CommittedMemoryV1;
|
|
73
|
+
/**
|
|
74
|
+
* Returns the record only when unexpired, owned by `query.owner`, and —
|
|
75
|
+
* when `query.suiteId` is set — visible in that suite (legacy atoms are
|
|
76
|
+
* invisible to every suite).
|
|
77
|
+
*/
|
|
78
|
+
get(memoryId: string, query: MemoryStoreQuery): CommittedMemoryV1 | undefined;
|
|
79
|
+
/** Lists active records owned by `query.owner`, optionally narrowed to one scope. */
|
|
80
|
+
list(query: MemoryStoreQuery, scope?: MemoryScopeV1): readonly CommittedMemoryV1[];
|
|
81
|
+
/**
|
|
82
|
+
* Suite-scoped listing with skip diagnostics: returns the records visible
|
|
83
|
+
* in `query.suiteId` plus how many legacy and foreign-suite records were
|
|
84
|
+
* excluded (expired records are excluded before the suite filter and are
|
|
85
|
+
* not counted here).
|
|
86
|
+
*/
|
|
87
|
+
listForSuite(query: MemoryStoreQuery & {
|
|
88
|
+
readonly suiteId: string;
|
|
89
|
+
}, scope?: MemoryScopeV1): MemoryStoreSuitePageV1;
|
|
90
|
+
stats(): MemoryStoreStats;
|
|
91
|
+
/**
|
|
92
|
+
* Physically removes one record from the canonical in-memory ledger — the
|
|
93
|
+
* store-side arm of the controlled purge gate (设计 §3.3: only the purge
|
|
94
|
+
* flow may destroy committed atoms). Call it exclusively from the purge
|
|
95
|
+
* path (suite memory facade / scheduler purge); ordinary business code
|
|
96
|
+
* never evicts. Returns true when the record existed. Expired-then-evicted
|
|
97
|
+
* and unexpired records are removed alike; a restart replay cannot
|
|
98
|
+
* resurrect an evicted atom once the durable replica is rewritten.
|
|
99
|
+
*/
|
|
100
|
+
evict(memoryId: string): boolean;
|
|
101
|
+
}
|
|
102
|
+
/** Creates a deterministic three-scope Foundation store. Expired records lose recall but stay ledgered. */
|
|
103
|
+
export declare function createMemoryStore(options: {
|
|
104
|
+
readonly retentionRegistry: RetentionModeRegistryV1;
|
|
105
|
+
readonly now?: () => number;
|
|
106
|
+
}): MemoryStoreV1;
|
|
107
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/memory/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — three-scope store with revisions, TTL, and retention\n * inputs (1C.1b).\n *\n * Scope decides which partition a committed atom belongs to (session / cycle /\n * long-term); retention decides how long it stays recallable. Retention mode\n * IDs are opaque strings resolved through a versioned registry provided by the\n * Profile — the Foundation never hardcodes business durations. Expired records\n * lose recall eligibility but are NOT physically purged here (physical purge\n * is the controlled 1C.6d gate).\n */\n\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryAtomV1, MemoryScopeV1, MemorySuiteFilterStatsV1 } from \"./foundation.ts\";\nimport { memorySuiteVisibility, validateMemoryAtomV1 } from \"./foundation.ts\";\n\n/** One retention mode published by a Profile's retention-mode registry. */\nexport interface RetentionModeDefinitionV1 {\n\treadonly id: string;\n\t/** Recall duration in ms. Absent = no expiry (e.g. `permanent`). */\n\treadonly durationMs?: number;\n\treadonly autoPurge: boolean;\n}\n\n/** Versioned retention-mode registry snapshot. */\nexport interface RetentionModeRegistryV1 {\n\treadonly policyVersion: string;\n\treadonly modes: readonly RetentionModeDefinitionV1[];\n\tget(id: string): RetentionModeDefinitionV1 | undefined;\n}\n\n/** Validates a retention-mode registry: parseable, finite, non-negative, unique, traceable. */\nexport function validateRetentionModeRegistry(value: unknown): RetentionModeRegistryV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(\"Retention mode registry must be a plain object\");\n\t}\n\tconst record = value as Record<string, unknown>;\n\tassertNonEmptyString(record.policyVersion, \"Retention mode registry.policyVersion\");\n\tif (!Array.isArray(record.modes) || record.modes.length === 0) {\n\t\tthrow new Error(\"Retention mode registry.modes must be a non-empty array\");\n\t}\n\tconst seen = new Set<string>();\n\tconst modes: RetentionModeDefinitionV1[] = record.modes.map((item, index) => {\n\t\tconst label = `Retention mode[${index}]`;\n\t\tif (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\tthrow new Error(`${label} must be a plain object`);\n\t\t}\n\t\tconst entry = item as Record<string, unknown>;\n\t\tassertNonEmptyString(entry.id, `${label}.id`);\n\t\tif (seen.has(entry.id)) throw new Error(`${label} duplicates id: ${entry.id}`);\n\t\tseen.add(entry.id);\n\t\tif (typeof entry.autoPurge !== \"boolean\") throw new Error(`${label}.autoPurge must be boolean`);\n\t\tif (entry.durationMs !== undefined) {\n\t\t\tif (typeof entry.durationMs !== \"number\" || !Number.isSafeInteger(entry.durationMs) || entry.durationMs <= 0) {\n\t\t\t\tthrow new Error(`${label}.durationMs must be a positive safe integer`);\n\t\t\t}\n\t\t}\n\t\treturn Object.freeze({\n\t\t\tid: entry.id,\n\t\t\t...(entry.durationMs === undefined ? {} : { durationMs: entry.durationMs }),\n\t\t\tautoPurge: entry.autoPurge,\n\t\t});\n\t});\n\treturn Object.freeze({\n\t\tpolicyVersion: record.policyVersion,\n\t\tmodes: Object.freeze(modes),\n\t\tget(id: string) {\n\t\t\treturn modes.find((mode) => mode.id === id);\n\t\t},\n\t});\n}\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n\n/** First-party default registry (short/standard/long/permanent). Durations live here, not in Foundation logic. */\nexport function createFirstPartyRetentionRegistry(): RetentionModeRegistryV1 {\n\tconst DAY = 24 * 60 * 60 * 1000;\n\treturn validateRetentionModeRegistry({\n\t\tpolicyVersion: \"retention@1\",\n\t\tmodes: [\n\t\t\t{ id: \"short\", durationMs: 7 * DAY, autoPurge: true },\n\t\t\t{ id: \"standard\", durationMs: 30 * DAY, autoPurge: true },\n\t\t\t{ id: \"long\", durationMs: 365 * DAY, autoPurge: true },\n\t\t\t{ id: \"permanent\", autoPurge: false },\n\t\t],\n\t});\n}\n\nexport interface MemoryStoreCommitOptions {\n\t/** Overrides the atom's retentionMode id at commit time (policy-selected). */\n\treadonly retentionModeId?: string;\n}\n\n/** A committed atom plus the store-assigned bookkeeping facts. */\nexport interface CommittedMemoryV1<TPayload extends JsonValue = JsonValue> {\n\treadonly atom: MemoryAtomV1<TPayload>;\n\t/** Monotonic across the whole store. */\n\treadonly storeRevision: number;\n\t/** Monotonic within the atom's scope partition. */\n\treadonly scopeRevision: number;\n\treadonly committedAt: number;\n\t/** Recall deadline computed from the retention registry. Absent = no expiry. */\n\treadonly purgeAt?: number;\n\treadonly retentionModeId: string;\n}\n\nexport interface MemoryStoreQuery {\n\treadonly owner: string;\n\t/**\n\t * Suite-scoped read boundary (方案系统设计 §6.1/§11). When set, only atoms\n\t * whose suiteId equals this value — plus explicitly promoted user-default\n\t * preferences — are visible; legacy (suiteId-less) atoms are skipped\n\t * fail-safe. When absent, no suite filtering happens (suite-unscoped read).\n\t */\n\treadonly suiteId?: string;\n}\n\nexport interface MemoryStoreStats {\n\treadonly active: number;\n\treadonly expired: number;\n\treadonly byScope: Readonly<Record<MemoryScopeV1, { active: number; expired: number }>>;\n}\n\n/** Result of {@link MemoryStoreV1.listForSuite}: visible records plus skip counters. */\nexport interface MemoryStoreSuitePageV1 {\n\treadonly records: readonly CommittedMemoryV1[];\n\t/** Excluded-record counters for the suite read boundary (设计 §11). */\n\treadonly filter: MemorySuiteFilterStatsV1;\n}\n\nexport interface MemoryStoreV1 {\n\tcommit(atom: MemoryAtomV1, options?: MemoryStoreCommitOptions): CommittedMemoryV1;\n\t/**\n\t * Returns the record only when unexpired, owned by `query.owner`, and —\n\t * when `query.suiteId` is set — visible in that suite (legacy atoms are\n\t * invisible to every suite).\n\t */\n\tget(memoryId: string, query: MemoryStoreQuery): CommittedMemoryV1 | undefined;\n\t/** Lists active records owned by `query.owner`, optionally narrowed to one scope. */\n\tlist(query: MemoryStoreQuery, scope?: MemoryScopeV1): readonly CommittedMemoryV1[];\n\t/**\n\t * Suite-scoped listing with skip diagnostics: returns the records visible\n\t * in `query.suiteId` plus how many legacy and foreign-suite records were\n\t * excluded (expired records are excluded before the suite filter and are\n\t * not counted here).\n\t */\n\tlistForSuite(query: MemoryStoreQuery & { readonly suiteId: string }, scope?: MemoryScopeV1): MemoryStoreSuitePageV1;\n\tstats(): MemoryStoreStats;\n\t/**\n\t * Physically removes one record from the canonical in-memory ledger — the\n\t * store-side arm of the controlled purge gate (设计 §3.3: only the purge\n\t * flow may destroy committed atoms). Call it exclusively from the purge\n\t * path (suite memory facade / scheduler purge); ordinary business code\n\t * never evicts. Returns true when the record existed. Expired-then-evicted\n\t * and unexpired records are removed alike; a restart replay cannot\n\t * resurrect an evicted atom once the durable replica is rewritten.\n\t */\n\tevict(memoryId: string): boolean;\n}\n\n/** Creates a deterministic three-scope Foundation store. Expired records lose recall but stay ledgered. */\nexport function createMemoryStore(options: {\n\treadonly retentionRegistry: RetentionModeRegistryV1;\n\treadonly now?: () => number;\n}): MemoryStoreV1 {\n\tconst registry = options.retentionRegistry;\n\tconst now = options.now ?? (() => Date.now());\n\tconst records = new Map<string, CommittedMemoryV1>();\n\tlet storeRevision = 0;\n\tconst scopeRevisions = new Map<MemoryScopeV1, number>();\n\n\tconst isExpired = (record: CommittedMemoryV1): boolean => record.purgeAt !== undefined && record.purgeAt <= now();\n\n\tconst requireOwner = (query: MemoryStoreQuery): string => {\n\t\tif (query === null || typeof query !== \"object\" || typeof query.owner !== \"string\" || query.owner.trim() === \"\") {\n\t\t\tthrow new Error(\"Memory store query requires an owner\");\n\t\t}\n\t\tif (query.suiteId !== undefined && (typeof query.suiteId !== \"string\" || query.suiteId.trim() === \"\")) {\n\t\t\tthrow new Error(\"Memory store query requires a non-empty suiteId when filtering by suite\");\n\t\t}\n\t\treturn query.owner;\n\t};\n\n\t/** Suite read boundary: `undefined` query.suiteId means no suite filtering. */\n\tconst isVisibleInSuite = (atom: MemoryAtomV1, suiteId: string | undefined): boolean =>\n\t\tsuiteId === undefined || memorySuiteVisibility(atom, suiteId) === \"visible\";\n\n\treturn {\n\t\tcommit(atom, commitOptions) {\n\t\t\tconst validated = validateMemoryAtomV1(atom);\n\t\t\t// Canonical atoms are immutable once committed (设计 §4): a second\n\t\t\t// commit with the same memoryId is a caller bug and must fail\n\t\t\t// explicitly instead of silently overwriting verified facts.\n\t\t\tif (records.has(validated.memoryId)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Memory already committed: ${validated.memoryId} (canonical atoms are immutable; use the candidate machine for dedupe)`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst modeId = commitOptions?.retentionModeId ?? validated.retentionMode;\n\t\t\tconst mode = registry.get(modeId);\n\t\t\tif (!mode) throw new Error(`Retention mode is not registered: ${modeId}`);\n\t\t\tstoreRevision += 1;\n\t\t\tconst scopeRevision = (scopeRevisions.get(validated.scope) ?? 0) + 1;\n\t\t\tscopeRevisions.set(validated.scope, scopeRevision);\n\t\t\tconst committedAt = now();\n\t\t\tconst committed: CommittedMemoryV1 = Object.freeze({\n\t\t\t\tatom: validated,\n\t\t\t\tstoreRevision,\n\t\t\t\tscopeRevision,\n\t\t\t\tcommittedAt,\n\t\t\t\t...(mode.durationMs === undefined ? {} : { purgeAt: committedAt + mode.durationMs }),\n\t\t\t\tretentionModeId: mode.id,\n\t\t\t});\n\t\t\trecords.set(validated.memoryId, committed);\n\t\t\treturn committed;\n\t\t},\n\t\tget(memoryId, query) {\n\t\t\tconst owner = requireOwner(query);\n\t\t\tconst record = records.get(memoryId);\n\t\t\tif (!record) return undefined;\n\t\t\tif (record.atom.owner !== owner) return undefined;\n\t\t\tif (!isVisibleInSuite(record.atom, query.suiteId)) return undefined;\n\t\t\tif (isExpired(record)) return undefined;\n\t\t\treturn record;\n\t\t},\n\t\tlist(query, scope) {\n\t\t\tconst owner = requireOwner(query);\n\t\t\tconst results: CommittedMemoryV1[] = [];\n\t\t\tfor (const record of records.values()) {\n\t\t\t\tif (record.atom.owner !== owner) continue;\n\t\t\t\tif (scope !== undefined && record.atom.scope !== scope) continue;\n\t\t\t\tif (!isVisibleInSuite(record.atom, query.suiteId)) continue;\n\t\t\t\tif (isExpired(record)) continue;\n\t\t\t\tresults.push(record);\n\t\t\t}\n\t\t\treturn Object.freeze(results.sort((left, right) => left.storeRevision - right.storeRevision));\n\t\t},\n\t\tlistForSuite(query, scope) {\n\t\t\trequireOwner(query);\n\t\t\tconst suiteId = query.suiteId;\n\t\t\tconst results: CommittedMemoryV1[] = [];\n\t\t\tlet legacySkipped = 0;\n\t\t\tlet foreignSuiteSkipped = 0;\n\t\t\tfor (const record of records.values()) {\n\t\t\t\tif (record.atom.owner !== query.owner) continue;\n\t\t\t\tif (scope !== undefined && record.atom.scope !== scope) continue;\n\t\t\t\tif (isExpired(record)) continue;\n\t\t\t\tconst visibility = memorySuiteVisibility(record.atom, suiteId);\n\t\t\t\tif (visibility === \"legacy\") {\n\t\t\t\t\tlegacySkipped += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (visibility === \"foreign-suite\") {\n\t\t\t\t\tforeignSuiteSkipped += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tresults.push(record);\n\t\t\t}\n\t\t\tresults.sort((left, right) => left.storeRevision - right.storeRevision);\n\t\t\treturn Object.freeze({\n\t\t\t\trecords: Object.freeze(results),\n\t\t\t\tfilter: Object.freeze({ legacySkipped, foreignSuiteSkipped }),\n\t\t\t});\n\t\t},\n\t\tstats() {\n\t\t\tconst bucket = () => ({ active: 0, expired: 0 });\n\t\t\tconst byScope: Record<MemoryScopeV1, { active: number; expired: number }> = {\n\t\t\t\tsession: bucket(),\n\t\t\t\tcycle: bucket(),\n\t\t\t\t\"long-term\": bucket(),\n\t\t\t};\n\t\t\tlet active = 0;\n\t\t\tlet expired = 0;\n\t\t\tfor (const record of records.values()) {\n\t\t\t\tconst target = byScope[record.atom.scope];\n\t\t\t\tif (isExpired(record)) {\n\t\t\t\t\texpired += 1;\n\t\t\t\t\ttarget.expired += 1;\n\t\t\t\t} else {\n\t\t\t\t\tactive += 1;\n\t\t\t\t\ttarget.active += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { active, expired, byScope: Object.freeze(byScope) };\n\t\t},\n\t\tevict(memoryId) {\n\t\t\tif (typeof memoryId !== \"string\" || memoryId.trim() === \"\") {\n\t\t\t\tthrow new Error(\"Memory store evict requires a memoryId\");\n\t\t\t}\n\t\t\treturn records.delete(memoryId);\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Foundation — three-scope store with revisions, TTL, and retention
|
|
3
|
+
* inputs (1C.1b).
|
|
4
|
+
*
|
|
5
|
+
* Scope decides which partition a committed atom belongs to (session / cycle /
|
|
6
|
+
* long-term); retention decides how long it stays recallable. Retention mode
|
|
7
|
+
* IDs are opaque strings resolved through a versioned registry provided by the
|
|
8
|
+
* Profile — the Foundation never hardcodes business durations. Expired records
|
|
9
|
+
* lose recall eligibility but are NOT physically purged here (physical purge
|
|
10
|
+
* is the controlled 1C.6d gate).
|
|
11
|
+
*/
|
|
12
|
+
import { memorySuiteVisibility, validateMemoryAtomV1 } from "./foundation.js";
|
|
13
|
+
/** Validates a retention-mode registry: parseable, finite, non-negative, unique, traceable. */
|
|
14
|
+
export function validateRetentionModeRegistry(value) {
|
|
15
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
16
|
+
throw new Error("Retention mode registry must be a plain object");
|
|
17
|
+
}
|
|
18
|
+
const record = value;
|
|
19
|
+
assertNonEmptyString(record.policyVersion, "Retention mode registry.policyVersion");
|
|
20
|
+
if (!Array.isArray(record.modes) || record.modes.length === 0) {
|
|
21
|
+
throw new Error("Retention mode registry.modes must be a non-empty array");
|
|
22
|
+
}
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
const modes = record.modes.map((item, index) => {
|
|
25
|
+
const label = `Retention mode[${index}]`;
|
|
26
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
27
|
+
throw new Error(`${label} must be a plain object`);
|
|
28
|
+
}
|
|
29
|
+
const entry = item;
|
|
30
|
+
assertNonEmptyString(entry.id, `${label}.id`);
|
|
31
|
+
if (seen.has(entry.id))
|
|
32
|
+
throw new Error(`${label} duplicates id: ${entry.id}`);
|
|
33
|
+
seen.add(entry.id);
|
|
34
|
+
if (typeof entry.autoPurge !== "boolean")
|
|
35
|
+
throw new Error(`${label}.autoPurge must be boolean`);
|
|
36
|
+
if (entry.durationMs !== undefined) {
|
|
37
|
+
if (typeof entry.durationMs !== "number" || !Number.isSafeInteger(entry.durationMs) || entry.durationMs <= 0) {
|
|
38
|
+
throw new Error(`${label}.durationMs must be a positive safe integer`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
id: entry.id,
|
|
43
|
+
...(entry.durationMs === undefined ? {} : { durationMs: entry.durationMs }),
|
|
44
|
+
autoPurge: entry.autoPurge,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
return Object.freeze({
|
|
48
|
+
policyVersion: record.policyVersion,
|
|
49
|
+
modes: Object.freeze(modes),
|
|
50
|
+
get(id) {
|
|
51
|
+
return modes.find((mode) => mode.id === id);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function assertNonEmptyString(value, label) {
|
|
56
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
57
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
58
|
+
}
|
|
59
|
+
/** First-party default registry (short/standard/long/permanent). Durations live here, not in Foundation logic. */
|
|
60
|
+
export function createFirstPartyRetentionRegistry() {
|
|
61
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
62
|
+
return validateRetentionModeRegistry({
|
|
63
|
+
policyVersion: "retention@1",
|
|
64
|
+
modes: [
|
|
65
|
+
{ id: "short", durationMs: 7 * DAY, autoPurge: true },
|
|
66
|
+
{ id: "standard", durationMs: 30 * DAY, autoPurge: true },
|
|
67
|
+
{ id: "long", durationMs: 365 * DAY, autoPurge: true },
|
|
68
|
+
{ id: "permanent", autoPurge: false },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Creates a deterministic three-scope Foundation store. Expired records lose recall but stay ledgered. */
|
|
73
|
+
export function createMemoryStore(options) {
|
|
74
|
+
const registry = options.retentionRegistry;
|
|
75
|
+
const now = options.now ?? (() => Date.now());
|
|
76
|
+
const records = new Map();
|
|
77
|
+
let storeRevision = 0;
|
|
78
|
+
const scopeRevisions = new Map();
|
|
79
|
+
const isExpired = (record) => record.purgeAt !== undefined && record.purgeAt <= now();
|
|
80
|
+
const requireOwner = (query) => {
|
|
81
|
+
if (query === null || typeof query !== "object" || typeof query.owner !== "string" || query.owner.trim() === "") {
|
|
82
|
+
throw new Error("Memory store query requires an owner");
|
|
83
|
+
}
|
|
84
|
+
if (query.suiteId !== undefined && (typeof query.suiteId !== "string" || query.suiteId.trim() === "")) {
|
|
85
|
+
throw new Error("Memory store query requires a non-empty suiteId when filtering by suite");
|
|
86
|
+
}
|
|
87
|
+
return query.owner;
|
|
88
|
+
};
|
|
89
|
+
/** Suite read boundary: `undefined` query.suiteId means no suite filtering. */
|
|
90
|
+
const isVisibleInSuite = (atom, suiteId) => suiteId === undefined || memorySuiteVisibility(atom, suiteId) === "visible";
|
|
91
|
+
return {
|
|
92
|
+
commit(atom, commitOptions) {
|
|
93
|
+
const validated = validateMemoryAtomV1(atom);
|
|
94
|
+
// Canonical atoms are immutable once committed (设计 §4): a second
|
|
95
|
+
// commit with the same memoryId is a caller bug and must fail
|
|
96
|
+
// explicitly instead of silently overwriting verified facts.
|
|
97
|
+
if (records.has(validated.memoryId)) {
|
|
98
|
+
throw new Error(`Memory already committed: ${validated.memoryId} (canonical atoms are immutable; use the candidate machine for dedupe)`);
|
|
99
|
+
}
|
|
100
|
+
const modeId = commitOptions?.retentionModeId ?? validated.retentionMode;
|
|
101
|
+
const mode = registry.get(modeId);
|
|
102
|
+
if (!mode)
|
|
103
|
+
throw new Error(`Retention mode is not registered: ${modeId}`);
|
|
104
|
+
storeRevision += 1;
|
|
105
|
+
const scopeRevision = (scopeRevisions.get(validated.scope) ?? 0) + 1;
|
|
106
|
+
scopeRevisions.set(validated.scope, scopeRevision);
|
|
107
|
+
const committedAt = now();
|
|
108
|
+
const committed = Object.freeze({
|
|
109
|
+
atom: validated,
|
|
110
|
+
storeRevision,
|
|
111
|
+
scopeRevision,
|
|
112
|
+
committedAt,
|
|
113
|
+
...(mode.durationMs === undefined ? {} : { purgeAt: committedAt + mode.durationMs }),
|
|
114
|
+
retentionModeId: mode.id,
|
|
115
|
+
});
|
|
116
|
+
records.set(validated.memoryId, committed);
|
|
117
|
+
return committed;
|
|
118
|
+
},
|
|
119
|
+
get(memoryId, query) {
|
|
120
|
+
const owner = requireOwner(query);
|
|
121
|
+
const record = records.get(memoryId);
|
|
122
|
+
if (!record)
|
|
123
|
+
return undefined;
|
|
124
|
+
if (record.atom.owner !== owner)
|
|
125
|
+
return undefined;
|
|
126
|
+
if (!isVisibleInSuite(record.atom, query.suiteId))
|
|
127
|
+
return undefined;
|
|
128
|
+
if (isExpired(record))
|
|
129
|
+
return undefined;
|
|
130
|
+
return record;
|
|
131
|
+
},
|
|
132
|
+
list(query, scope) {
|
|
133
|
+
const owner = requireOwner(query);
|
|
134
|
+
const results = [];
|
|
135
|
+
for (const record of records.values()) {
|
|
136
|
+
if (record.atom.owner !== owner)
|
|
137
|
+
continue;
|
|
138
|
+
if (scope !== undefined && record.atom.scope !== scope)
|
|
139
|
+
continue;
|
|
140
|
+
if (!isVisibleInSuite(record.atom, query.suiteId))
|
|
141
|
+
continue;
|
|
142
|
+
if (isExpired(record))
|
|
143
|
+
continue;
|
|
144
|
+
results.push(record);
|
|
145
|
+
}
|
|
146
|
+
return Object.freeze(results.sort((left, right) => left.storeRevision - right.storeRevision));
|
|
147
|
+
},
|
|
148
|
+
listForSuite(query, scope) {
|
|
149
|
+
requireOwner(query);
|
|
150
|
+
const suiteId = query.suiteId;
|
|
151
|
+
const results = [];
|
|
152
|
+
let legacySkipped = 0;
|
|
153
|
+
let foreignSuiteSkipped = 0;
|
|
154
|
+
for (const record of records.values()) {
|
|
155
|
+
if (record.atom.owner !== query.owner)
|
|
156
|
+
continue;
|
|
157
|
+
if (scope !== undefined && record.atom.scope !== scope)
|
|
158
|
+
continue;
|
|
159
|
+
if (isExpired(record))
|
|
160
|
+
continue;
|
|
161
|
+
const visibility = memorySuiteVisibility(record.atom, suiteId);
|
|
162
|
+
if (visibility === "legacy") {
|
|
163
|
+
legacySkipped += 1;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (visibility === "foreign-suite") {
|
|
167
|
+
foreignSuiteSkipped += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
results.push(record);
|
|
171
|
+
}
|
|
172
|
+
results.sort((left, right) => left.storeRevision - right.storeRevision);
|
|
173
|
+
return Object.freeze({
|
|
174
|
+
records: Object.freeze(results),
|
|
175
|
+
filter: Object.freeze({ legacySkipped, foreignSuiteSkipped }),
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
stats() {
|
|
179
|
+
const bucket = () => ({ active: 0, expired: 0 });
|
|
180
|
+
const byScope = {
|
|
181
|
+
session: bucket(),
|
|
182
|
+
cycle: bucket(),
|
|
183
|
+
"long-term": bucket(),
|
|
184
|
+
};
|
|
185
|
+
let active = 0;
|
|
186
|
+
let expired = 0;
|
|
187
|
+
for (const record of records.values()) {
|
|
188
|
+
const target = byScope[record.atom.scope];
|
|
189
|
+
if (isExpired(record)) {
|
|
190
|
+
expired += 1;
|
|
191
|
+
target.expired += 1;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
active += 1;
|
|
195
|
+
target.active += 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { active, expired, byScope: Object.freeze(byScope) };
|
|
199
|
+
},
|
|
200
|
+
evict(memoryId) {
|
|
201
|
+
if (typeof memoryId !== "string" || memoryId.trim() === "") {
|
|
202
|
+
throw new Error("Memory store evict requires a memoryId");
|
|
203
|
+
}
|
|
204
|
+
return records.delete(memoryId);
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/memory/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — three-scope store with revisions, TTL, and retention\n * inputs (1C.1b).\n *\n * Scope decides which partition a committed atom belongs to (session / cycle /\n * long-term); retention decides how long it stays recallable. Retention mode\n * IDs are opaque strings resolved through a versioned registry provided by the\n * Profile — the Foundation never hardcodes business durations. Expired records\n * lose recall eligibility but are NOT physically purged here (physical purge\n * is the controlled 1C.6d gate).\n */\n\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryAtomV1, MemoryScopeV1, MemorySuiteFilterStatsV1 } from \"./foundation.ts\";\nimport { memorySuiteVisibility, validateMemoryAtomV1 } from \"./foundation.ts\";\n\n/** One retention mode published by a Profile's retention-mode registry. */\nexport interface RetentionModeDefinitionV1 {\n\treadonly id: string;\n\t/** Recall duration in ms. Absent = no expiry (e.g. `permanent`). */\n\treadonly durationMs?: number;\n\treadonly autoPurge: boolean;\n}\n\n/** Versioned retention-mode registry snapshot. */\nexport interface RetentionModeRegistryV1 {\n\treadonly policyVersion: string;\n\treadonly modes: readonly RetentionModeDefinitionV1[];\n\tget(id: string): RetentionModeDefinitionV1 | undefined;\n}\n\n/** Validates a retention-mode registry: parseable, finite, non-negative, unique, traceable. */\nexport function validateRetentionModeRegistry(value: unknown): RetentionModeRegistryV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(\"Retention mode registry must be a plain object\");\n\t}\n\tconst record = value as Record<string, unknown>;\n\tassertNonEmptyString(record.policyVersion, \"Retention mode registry.policyVersion\");\n\tif (!Array.isArray(record.modes) || record.modes.length === 0) {\n\t\tthrow new Error(\"Retention mode registry.modes must be a non-empty array\");\n\t}\n\tconst seen = new Set<string>();\n\tconst modes: RetentionModeDefinitionV1[] = record.modes.map((item, index) => {\n\t\tconst label = `Retention mode[${index}]`;\n\t\tif (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\tthrow new Error(`${label} must be a plain object`);\n\t\t}\n\t\tconst entry = item as Record<string, unknown>;\n\t\tassertNonEmptyString(entry.id, `${label}.id`);\n\t\tif (seen.has(entry.id)) throw new Error(`${label} duplicates id: ${entry.id}`);\n\t\tseen.add(entry.id);\n\t\tif (typeof entry.autoPurge !== \"boolean\") throw new Error(`${label}.autoPurge must be boolean`);\n\t\tif (entry.durationMs !== undefined) {\n\t\t\tif (typeof entry.durationMs !== \"number\" || !Number.isSafeInteger(entry.durationMs) || entry.durationMs <= 0) {\n\t\t\t\tthrow new Error(`${label}.durationMs must be a positive safe integer`);\n\t\t\t}\n\t\t}\n\t\treturn Object.freeze({\n\t\t\tid: entry.id,\n\t\t\t...(entry.durationMs === undefined ? {} : { durationMs: entry.durationMs }),\n\t\t\tautoPurge: entry.autoPurge,\n\t\t});\n\t});\n\treturn Object.freeze({\n\t\tpolicyVersion: record.policyVersion,\n\t\tmodes: Object.freeze(modes),\n\t\tget(id: string) {\n\t\t\treturn modes.find((mode) => mode.id === id);\n\t\t},\n\t});\n}\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n\n/** First-party default registry (short/standard/long/permanent). Durations live here, not in Foundation logic. */\nexport function createFirstPartyRetentionRegistry(): RetentionModeRegistryV1 {\n\tconst DAY = 24 * 60 * 60 * 1000;\n\treturn validateRetentionModeRegistry({\n\t\tpolicyVersion: \"retention@1\",\n\t\tmodes: [\n\t\t\t{ id: \"short\", durationMs: 7 * DAY, autoPurge: true },\n\t\t\t{ id: \"standard\", durationMs: 30 * DAY, autoPurge: true },\n\t\t\t{ id: \"long\", durationMs: 365 * DAY, autoPurge: true },\n\t\t\t{ id: \"permanent\", autoPurge: false },\n\t\t],\n\t});\n}\n\nexport interface MemoryStoreCommitOptions {\n\t/** Overrides the atom's retentionMode id at commit time (policy-selected). */\n\treadonly retentionModeId?: string;\n}\n\n/** A committed atom plus the store-assigned bookkeeping facts. */\nexport interface CommittedMemoryV1<TPayload extends JsonValue = JsonValue> {\n\treadonly atom: MemoryAtomV1<TPayload>;\n\t/** Monotonic across the whole store. */\n\treadonly storeRevision: number;\n\t/** Monotonic within the atom's scope partition. */\n\treadonly scopeRevision: number;\n\treadonly committedAt: number;\n\t/** Recall deadline computed from the retention registry. Absent = no expiry. */\n\treadonly purgeAt?: number;\n\treadonly retentionModeId: string;\n}\n\nexport interface MemoryStoreQuery {\n\treadonly owner: string;\n\t/**\n\t * Suite-scoped read boundary (方案系统设计 §6.1/§11). When set, only atoms\n\t * whose suiteId equals this value — plus explicitly promoted user-default\n\t * preferences — are visible; legacy (suiteId-less) atoms are skipped\n\t * fail-safe. When absent, no suite filtering happens (suite-unscoped read).\n\t */\n\treadonly suiteId?: string;\n}\n\nexport interface MemoryStoreStats {\n\treadonly active: number;\n\treadonly expired: number;\n\treadonly byScope: Readonly<Record<MemoryScopeV1, { active: number; expired: number }>>;\n}\n\n/** Result of {@link MemoryStoreV1.listForSuite}: visible records plus skip counters. */\nexport interface MemoryStoreSuitePageV1 {\n\treadonly records: readonly CommittedMemoryV1[];\n\t/** Excluded-record counters for the suite read boundary (设计 §11). */\n\treadonly filter: MemorySuiteFilterStatsV1;\n}\n\nexport interface MemoryStoreV1 {\n\tcommit(atom: MemoryAtomV1, options?: MemoryStoreCommitOptions): CommittedMemoryV1;\n\t/**\n\t * Returns the record only when unexpired, owned by `query.owner`, and —\n\t * when `query.suiteId` is set — visible in that suite (legacy atoms are\n\t * invisible to every suite).\n\t */\n\tget(memoryId: string, query: MemoryStoreQuery): CommittedMemoryV1 | undefined;\n\t/** Lists active records owned by `query.owner`, optionally narrowed to one scope. */\n\tlist(query: MemoryStoreQuery, scope?: MemoryScopeV1): readonly CommittedMemoryV1[];\n\t/**\n\t * Suite-scoped listing with skip diagnostics: returns the records visible\n\t * in `query.suiteId` plus how many legacy and foreign-suite records were\n\t * excluded (expired records are excluded before the suite filter and are\n\t * not counted here).\n\t */\n\tlistForSuite(query: MemoryStoreQuery & { readonly suiteId: string }, scope?: MemoryScopeV1): MemoryStoreSuitePageV1;\n\tstats(): MemoryStoreStats;\n\t/**\n\t * Physically removes one record from the canonical in-memory ledger — the\n\t * store-side arm of the controlled purge gate (设计 §3.3: only the purge\n\t * flow may destroy committed atoms). Call it exclusively from the purge\n\t * path (suite memory facade / scheduler purge); ordinary business code\n\t * never evicts. Returns true when the record existed. Expired-then-evicted\n\t * and unexpired records are removed alike; a restart replay cannot\n\t * resurrect an evicted atom once the durable replica is rewritten.\n\t */\n\tevict(memoryId: string): boolean;\n}\n\n/** Creates a deterministic three-scope Foundation store. Expired records lose recall but stay ledgered. */\nexport function createMemoryStore(options: {\n\treadonly retentionRegistry: RetentionModeRegistryV1;\n\treadonly now?: () => number;\n}): MemoryStoreV1 {\n\tconst registry = options.retentionRegistry;\n\tconst now = options.now ?? (() => Date.now());\n\tconst records = new Map<string, CommittedMemoryV1>();\n\tlet storeRevision = 0;\n\tconst scopeRevisions = new Map<MemoryScopeV1, number>();\n\n\tconst isExpired = (record: CommittedMemoryV1): boolean => record.purgeAt !== undefined && record.purgeAt <= now();\n\n\tconst requireOwner = (query: MemoryStoreQuery): string => {\n\t\tif (query === null || typeof query !== \"object\" || typeof query.owner !== \"string\" || query.owner.trim() === \"\") {\n\t\t\tthrow new Error(\"Memory store query requires an owner\");\n\t\t}\n\t\tif (query.suiteId !== undefined && (typeof query.suiteId !== \"string\" || query.suiteId.trim() === \"\")) {\n\t\t\tthrow new Error(\"Memory store query requires a non-empty suiteId when filtering by suite\");\n\t\t}\n\t\treturn query.owner;\n\t};\n\n\t/** Suite read boundary: `undefined` query.suiteId means no suite filtering. */\n\tconst isVisibleInSuite = (atom: MemoryAtomV1, suiteId: string | undefined): boolean =>\n\t\tsuiteId === undefined || memorySuiteVisibility(atom, suiteId) === \"visible\";\n\n\treturn {\n\t\tcommit(atom, commitOptions) {\n\t\t\tconst validated = validateMemoryAtomV1(atom);\n\t\t\t// Canonical atoms are immutable once committed (设计 §4): a second\n\t\t\t// commit with the same memoryId is a caller bug and must fail\n\t\t\t// explicitly instead of silently overwriting verified facts.\n\t\t\tif (records.has(validated.memoryId)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Memory already committed: ${validated.memoryId} (canonical atoms are immutable; use the candidate machine for dedupe)`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst modeId = commitOptions?.retentionModeId ?? validated.retentionMode;\n\t\t\tconst mode = registry.get(modeId);\n\t\t\tif (!mode) throw new Error(`Retention mode is not registered: ${modeId}`);\n\t\t\tstoreRevision += 1;\n\t\t\tconst scopeRevision = (scopeRevisions.get(validated.scope) ?? 0) + 1;\n\t\t\tscopeRevisions.set(validated.scope, scopeRevision);\n\t\t\tconst committedAt = now();\n\t\t\tconst committed: CommittedMemoryV1 = Object.freeze({\n\t\t\t\tatom: validated,\n\t\t\t\tstoreRevision,\n\t\t\t\tscopeRevision,\n\t\t\t\tcommittedAt,\n\t\t\t\t...(mode.durationMs === undefined ? {} : { purgeAt: committedAt + mode.durationMs }),\n\t\t\t\tretentionModeId: mode.id,\n\t\t\t});\n\t\t\trecords.set(validated.memoryId, committed);\n\t\t\treturn committed;\n\t\t},\n\t\tget(memoryId, query) {\n\t\t\tconst owner = requireOwner(query);\n\t\t\tconst record = records.get(memoryId);\n\t\t\tif (!record) return undefined;\n\t\t\tif (record.atom.owner !== owner) return undefined;\n\t\t\tif (!isVisibleInSuite(record.atom, query.suiteId)) return undefined;\n\t\t\tif (isExpired(record)) return undefined;\n\t\t\treturn record;\n\t\t},\n\t\tlist(query, scope) {\n\t\t\tconst owner = requireOwner(query);\n\t\t\tconst results: CommittedMemoryV1[] = [];\n\t\t\tfor (const record of records.values()) {\n\t\t\t\tif (record.atom.owner !== owner) continue;\n\t\t\t\tif (scope !== undefined && record.atom.scope !== scope) continue;\n\t\t\t\tif (!isVisibleInSuite(record.atom, query.suiteId)) continue;\n\t\t\t\tif (isExpired(record)) continue;\n\t\t\t\tresults.push(record);\n\t\t\t}\n\t\t\treturn Object.freeze(results.sort((left, right) => left.storeRevision - right.storeRevision));\n\t\t},\n\t\tlistForSuite(query, scope) {\n\t\t\trequireOwner(query);\n\t\t\tconst suiteId = query.suiteId;\n\t\t\tconst results: CommittedMemoryV1[] = [];\n\t\t\tlet legacySkipped = 0;\n\t\t\tlet foreignSuiteSkipped = 0;\n\t\t\tfor (const record of records.values()) {\n\t\t\t\tif (record.atom.owner !== query.owner) continue;\n\t\t\t\tif (scope !== undefined && record.atom.scope !== scope) continue;\n\t\t\t\tif (isExpired(record)) continue;\n\t\t\t\tconst visibility = memorySuiteVisibility(record.atom, suiteId);\n\t\t\t\tif (visibility === \"legacy\") {\n\t\t\t\t\tlegacySkipped += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (visibility === \"foreign-suite\") {\n\t\t\t\t\tforeignSuiteSkipped += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tresults.push(record);\n\t\t\t}\n\t\t\tresults.sort((left, right) => left.storeRevision - right.storeRevision);\n\t\t\treturn Object.freeze({\n\t\t\t\trecords: Object.freeze(results),\n\t\t\t\tfilter: Object.freeze({ legacySkipped, foreignSuiteSkipped }),\n\t\t\t});\n\t\t},\n\t\tstats() {\n\t\t\tconst bucket = () => ({ active: 0, expired: 0 });\n\t\t\tconst byScope: Record<MemoryScopeV1, { active: number; expired: number }> = {\n\t\t\t\tsession: bucket(),\n\t\t\t\tcycle: bucket(),\n\t\t\t\t\"long-term\": bucket(),\n\t\t\t};\n\t\t\tlet active = 0;\n\t\t\tlet expired = 0;\n\t\t\tfor (const record of records.values()) {\n\t\t\t\tconst target = byScope[record.atom.scope];\n\t\t\t\tif (isExpired(record)) {\n\t\t\t\t\texpired += 1;\n\t\t\t\t\ttarget.expired += 1;\n\t\t\t\t} else {\n\t\t\t\t\tactive += 1;\n\t\t\t\t\ttarget.active += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { active, expired, byScope: Object.freeze(byScope) };\n\t\t},\n\t\tevict(memoryId) {\n\t\t\tif (typeof memoryId !== \"string\" || memoryId.trim() === \"\") {\n\t\t\t\tthrow new Error(\"Memory store evict requires a memoryId\");\n\t\t\t}\n\t\t\treturn records.delete(memoryId);\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suite memory facade (M5, 方案系统设计 §6.1 + 方案实施计划 §10) — the library
|
|
3
|
+
* surface a host uses to LIST and FORGET one suite's memories.
|
|
4
|
+
*
|
|
5
|
+
* The listing is the user-visible control surface for assistant-style personal
|
|
6
|
+
* memory (显式管理命令:列出/忘记 — controllability is a hard requirement when
|
|
7
|
+
* personal information is stored). Forgetting is the M4 suite-deletion hook:
|
|
8
|
+
* the atom set circled by suiteId goes through the canonical purge gate
|
|
9
|
+
* (user-immediate or profile-policy authorization) plus the purge journal, so
|
|
10
|
+
* authorization is structured, partial failures stay `purge_eligible` for an
|
|
11
|
+
* idempotent retry, and every step leaves an audit record. The physical
|
|
12
|
+
* replica purge is injected — this facade never reaches into store internals.
|
|
13
|
+
*/
|
|
14
|
+
import type { JsonValue } from "@agent-forge/plugin-sdk";
|
|
15
|
+
import type { MemoryKindV1, MemoryPreferenceEnvelopeV1, MemoryScopeV1, MemorySuiteFilterStatsV1 } from "./foundation.ts";
|
|
16
|
+
import type { MemoryPurgeGateV1 } from "./purge.ts";
|
|
17
|
+
import type { MemoryPurgeJournalV1 } from "./purge-journal.ts";
|
|
18
|
+
import type { MemoryStoreV1 } from "./store.ts";
|
|
19
|
+
/**
|
|
20
|
+
* Structured authorization for forgetting a suite. Suite deletion is a bulk,
|
|
21
|
+
* user- or policy-initiated destructive action, so the unstructured
|
|
22
|
+
* `authorizedBy`-only path and `retention-expiry` mode are not accepted.
|
|
23
|
+
*/
|
|
24
|
+
export type SuiteForgetAuthorizationRefV1 = {
|
|
25
|
+
readonly mode: "user-immediate";
|
|
26
|
+
readonly issuedAt: number;
|
|
27
|
+
readonly issuedBy: string;
|
|
28
|
+
readonly confirmationRef: string;
|
|
29
|
+
} | {
|
|
30
|
+
readonly mode: "profile-policy";
|
|
31
|
+
readonly issuedAt: number;
|
|
32
|
+
readonly issuedBy: string;
|
|
33
|
+
readonly policyServiceRef: string;
|
|
34
|
+
};
|
|
35
|
+
/** One user-visible suite memory entry (列出语义). */
|
|
36
|
+
export interface SuiteMemoryListEntryV1 {
|
|
37
|
+
readonly memoryId: string;
|
|
38
|
+
readonly memoryKind: MemoryKindV1;
|
|
39
|
+
readonly isPreference: boolean;
|
|
40
|
+
readonly scope: MemoryScopeV1;
|
|
41
|
+
readonly occurredAt: string;
|
|
42
|
+
readonly payload: JsonValue;
|
|
43
|
+
/** Present for preference atoms: the preference envelope summary. */
|
|
44
|
+
readonly preference?: {
|
|
45
|
+
readonly subject: MemoryPreferenceEnvelopeV1["subject"];
|
|
46
|
+
readonly key: string;
|
|
47
|
+
readonly scopeLevel: MemoryPreferenceEnvelopeV1["scope"]["level"];
|
|
48
|
+
/** True when explicitly promoted (user-default + confirmedAt) — cross-suite visible. */
|
|
49
|
+
readonly crossSuiteVisible: boolean;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export interface SuiteMemoryListResultV1 {
|
|
53
|
+
readonly suiteId: string;
|
|
54
|
+
readonly owner: string;
|
|
55
|
+
/** Deterministic order: store revision ascending (commit order). */
|
|
56
|
+
readonly entries: readonly SuiteMemoryListEntryV1[];
|
|
57
|
+
readonly preferenceCount: number;
|
|
58
|
+
readonly factCount: number;
|
|
59
|
+
/** Legacy/foreign-suite records excluded by the suite read boundary (设计 §11). */
|
|
60
|
+
readonly filter: MemorySuiteFilterStatsV1;
|
|
61
|
+
}
|
|
62
|
+
export interface SuiteMemoryFacadeDepsV1 {
|
|
63
|
+
readonly store: MemoryStoreV1;
|
|
64
|
+
readonly purgeGate: MemoryPurgeGateV1;
|
|
65
|
+
readonly purgeJournal: MemoryPurgeJournalV1;
|
|
66
|
+
readonly now?: () => number;
|
|
67
|
+
/**
|
|
68
|
+
* index 副本 (如 memory-vector-index, 混合检索工程化) 的物理清除回调。缺省
|
|
69
|
+
* undefined 时 index 副本分派为 no-op 且不报错——向量通道未装配 (off) 或
|
|
70
|
+
* 不可用 (disabled) 时索引里没有本会话写入的行, 无物可清。canonical 及其余
|
|
71
|
+
* 副本仍走 `purgeMemories` 回调 (行为零变化)。
|
|
72
|
+
*/
|
|
73
|
+
readonly purgeIndexReplica?: (memoryIds: readonly string[]) => void;
|
|
74
|
+
}
|
|
75
|
+
export interface ListSuiteMemoriesInputV1 {
|
|
76
|
+
readonly owner: string;
|
|
77
|
+
readonly suiteId: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The suite read-boundary DOMAIN a memory operation is scoped to: a real suite
|
|
81
|
+
* (方案) membership, or the "legacy" sentinel domain for atoms recorded
|
|
82
|
+
* without a suite binding (方案系统设计 §6.1). Membership, distinct from the
|
|
83
|
+
* read boundary: an atom belongs to exactly one domain — the suite stored in
|
|
84
|
+
* its `suiteId`, or the legacy domain when absent. A promoted user-default
|
|
85
|
+
* preference is READABLE from every suite but remains a member of its origin
|
|
86
|
+
* domain and is listed/forgotten only there.
|
|
87
|
+
*/
|
|
88
|
+
export type SuiteMemoryDomainV1 = {
|
|
89
|
+
readonly kind: "suite";
|
|
90
|
+
readonly suiteId: string;
|
|
91
|
+
} | {
|
|
92
|
+
readonly kind: "legacy";
|
|
93
|
+
};
|
|
94
|
+
/** Visible name of a domain: the suite id, or the "legacy" sentinel. */
|
|
95
|
+
export declare function suiteMemoryDomainName(domain: SuiteMemoryDomainV1): string;
|
|
96
|
+
/**
|
|
97
|
+
* Lists one suite's memories with preference/fact classification and
|
|
98
|
+
* membership skip counters. Promoted user-default preferences stay listed
|
|
99
|
+
* under their origin suite — only their READ visibility crosses suites.
|
|
100
|
+
*/
|
|
101
|
+
export declare function listSuiteMemories(deps: Pick<SuiteMemoryFacadeDepsV1, "store">, input: ListSuiteMemoriesInputV1): SuiteMemoryListResultV1;
|
|
102
|
+
/** Result of {@link listDomainMemories}: one domain's entries plus skip counters. */
|
|
103
|
+
export interface DomainMemoryListResultV1 {
|
|
104
|
+
/** The suite id, or the "legacy" sentinel for the no-suite domain. */
|
|
105
|
+
readonly domain: string;
|
|
106
|
+
readonly owner: string;
|
|
107
|
+
/** Deterministic order: store revision ascending (commit order). */
|
|
108
|
+
readonly entries: readonly SuiteMemoryListEntryV1[];
|
|
109
|
+
readonly preferenceCount: number;
|
|
110
|
+
readonly factCount: number;
|
|
111
|
+
/** Excluded-record counters for the other domains (设计 §11). */
|
|
112
|
+
readonly filter: MemorySuiteFilterStatsV1;
|
|
113
|
+
}
|
|
114
|
+
export interface ListDomainMemoriesInputV1 {
|
|
115
|
+
readonly owner: string;
|
|
116
|
+
readonly domain: SuiteMemoryDomainV1;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Lists one read-boundary DOMAIN's memories (suite membership or the legacy
|
|
120
|
+
* no-suite domain). The legacy domain lists exactly the suite-less atoms;
|
|
121
|
+
* suite-bound atoms count into `foreignSuiteSkipped`.
|
|
122
|
+
*/
|
|
123
|
+
export declare function listDomainMemories(deps: Pick<SuiteMemoryFacadeDepsV1, "store">, input: ListDomainMemoriesInputV1): DomainMemoryListResultV1;
|
|
124
|
+
export interface ForgetSuiteMemoriesInputV1 {
|
|
125
|
+
readonly owner: string;
|
|
126
|
+
readonly suiteId: string;
|
|
127
|
+
readonly authorizedBy: string;
|
|
128
|
+
readonly authorizationRef: SuiteForgetAuthorizationRefV1;
|
|
129
|
+
}
|
|
130
|
+
export type SuiteForgetResultV1 = {
|
|
131
|
+
readonly status: "nothing_to_purge";
|
|
132
|
+
readonly suiteId: string;
|
|
133
|
+
readonly filter: MemorySuiteFilterStatsV1;
|
|
134
|
+
} | {
|
|
135
|
+
readonly status: "completed" | "purge_eligible";
|
|
136
|
+
readonly suiteId: string;
|
|
137
|
+
readonly batchId: string;
|
|
138
|
+
readonly memoryIds: readonly string[];
|
|
139
|
+
readonly confirmedReplicas: readonly string[];
|
|
140
|
+
readonly failedReplicas?: readonly {
|
|
141
|
+
readonly replicaId: string;
|
|
142
|
+
readonly error: string;
|
|
143
|
+
}[];
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Forgets every memory belonging to one suite (membership = the atom's
|
|
147
|
+
* `suiteId`): circles the atom set, runs it through the purge gate
|
|
148
|
+
* (`user-immediate` requires `confirmationRef`, `profile-policy` requires
|
|
149
|
+
* `policyServiceRef`), journals authorization and per-replica confirmations,
|
|
150
|
+
* and completes the journal only when the barrier fully confirmed. A partial
|
|
151
|
+
* failure keeps the batch `purge_eligible` — retry with
|
|
152
|
+
* {@link retrySuiteForget} using the returned batchId. `purgeMemories` is the
|
|
153
|
+
* host-injected canonical-replica purge; it must be idempotent (the gate
|
|
154
|
+
* re-runs it on retry). Batch ids come from the gate's `batchIdFactory`
|
|
155
|
+
* (gate construction option).
|
|
156
|
+
*/
|
|
157
|
+
export declare function forgetSuiteMemories(deps: SuiteMemoryFacadeDepsV1, input: ForgetSuiteMemoriesInputV1, purgeMemories: (memoryIds: readonly string[]) => void): SuiteForgetResultV1;
|
|
158
|
+
/**
|
|
159
|
+
* Idempotent retry of a `purge_eligible` suite-forget batch. Unknown batch ids
|
|
160
|
+
* throw; already-completed batches are a no-op that reports `completed`
|
|
161
|
+
* (gate semantics).
|
|
162
|
+
*/
|
|
163
|
+
export declare function retrySuiteForget(deps: Pick<SuiteMemoryFacadeDepsV1, "purgeGate" | "purgeJournal" | "purgeIndexReplica" | "now">, input: {
|
|
164
|
+
readonly batchId: string;
|
|
165
|
+
readonly suiteId: string;
|
|
166
|
+
}, purgeMemories: (memoryIds: readonly string[]) => void): SuiteForgetResultV1;
|
|
167
|
+
export interface ForgetDomainMemoryInputV1 {
|
|
168
|
+
readonly owner: string;
|
|
169
|
+
/** The read-boundary domain the CALLER is operating in (tool-side isolation). */
|
|
170
|
+
readonly domain: SuiteMemoryDomainV1;
|
|
171
|
+
/** The single memory to forget; it must be a member of the caller's domain. */
|
|
172
|
+
readonly memoryId: string;
|
|
173
|
+
readonly authorizedBy: string;
|
|
174
|
+
readonly authorizationRef: SuiteForgetAuthorizationRefV1;
|
|
175
|
+
}
|
|
176
|
+
export type DomainMemoryForgetResultV1 =
|
|
177
|
+
/** No memory with this id under the owner. */
|
|
178
|
+
{
|
|
179
|
+
readonly status: "not_found";
|
|
180
|
+
readonly memoryId: string;
|
|
181
|
+
}
|
|
182
|
+
/** The memory exists but belongs to another domain — refused, no gate run. */
|
|
183
|
+
| {
|
|
184
|
+
readonly status: "foreign_domain";
|
|
185
|
+
readonly memoryId: string;
|
|
186
|
+
} | {
|
|
187
|
+
readonly status: "completed" | "purge_eligible";
|
|
188
|
+
readonly batchId: string;
|
|
189
|
+
readonly memoryIds: readonly string[];
|
|
190
|
+
readonly confirmedReplicas: readonly string[];
|
|
191
|
+
readonly failedReplicas?: readonly {
|
|
192
|
+
readonly replicaId: string;
|
|
193
|
+
readonly error: string;
|
|
194
|
+
}[];
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Forgets ONE memory on behalf of a caller scoped to a read-boundary domain
|
|
198
|
+
* (assistant 场景可控性硬需求, 方案系统设计 §6.1): the atom must be a member of
|
|
199
|
+
* the caller's domain (its `suiteId` matches, or it is suite-less for the
|
|
200
|
+
* legacy domain) — a caller can never forget another suite's atoms by id.
|
|
201
|
+
* Deletion goes through the canonical purge gate (user-immediate or
|
|
202
|
+
* profile-policy authorization) plus the purge journal, exactly like
|
|
203
|
+
* {@link forgetSuiteMemories}; the injected `purgeMemories` callback performs
|
|
204
|
+
* the physical replica purge (e.g. the durable ledger rewrite plus store
|
|
205
|
+
* eviction) and must be idempotent for `purge_eligible` retries.
|
|
206
|
+
*/
|
|
207
|
+
export declare function forgetDomainMemory(deps: SuiteMemoryFacadeDepsV1, input: ForgetDomainMemoryInputV1, purgeMemories: (memoryIds: readonly string[]) => void): DomainMemoryForgetResultV1;
|
|
208
|
+
//# sourceMappingURL=suite-memory.d.ts.map
|