@oscharko-dev/keiko-memory-governance 0.2.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/dist/.tsbuildinfo +1 -0
- package/dist/conflict.d.ts +9 -0
- package/dist/conflict.d.ts.map +1 -0
- package/dist/conflict.js +236 -0
- package/dist/correction.d.ts +17 -0
- package/dist/correction.d.ts.map +1 -0
- package/dist/correction.js +69 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +15 -0
- package/dist/forget.d.ts +5 -0
- package/dist/forget.d.ts.map +1 -0
- package/dist/forget.js +142 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/maintenance.d.ts +33 -0
- package/dist/maintenance.d.ts.map +1 -0
- package/dist/maintenance.js +180 -0
- package/dist/retention.d.ts +5 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +54 -0
- package/dist/status-ops.d.ts +6 -0
- package/dist/status-ops.d.ts.map +1 -0
- package/dist/status-ops.js +86 -0
- package/dist/suppression.d.ts +11 -0
- package/dist/suppression.d.ts.map +1 -0
- package/dist/suppression.js +85 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +32 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -0
- package/package.json +31 -0
package/dist/forget.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Forget / delete selection + envelope construction.
|
|
2
|
+
//
|
|
3
|
+
// Two pure functions:
|
|
4
|
+
//
|
|
5
|
+
// selectMemoriesForForget — filters a caller-supplied MemoryRecord array down to the
|
|
6
|
+
// subset matched by a ForgetSelector. Default `protectPinned: true` excludes pinned
|
|
7
|
+
// memories (the user must explicitly unpin first). Default `protectArchived: false`
|
|
8
|
+
// does NOT exclude archived memories (they are already out of active retrieval; a
|
|
9
|
+
// retention sweep is allowed to re-select them).
|
|
10
|
+
//
|
|
11
|
+
// buildForgetOperations — maps each selected MemoryRecord to a MemoryForget envelope.
|
|
12
|
+
// The contracts type literally pins `userAcknowledgedDestructive` to the literal
|
|
13
|
+
// `true`, so the envelope cannot structurally be constructed without acknowledgement.
|
|
14
|
+
// Every envelope is revalidated through validateMemoryForget before returning.
|
|
15
|
+
//
|
|
16
|
+
// The two-stage shape is deliberate: callers can present the selection to the user for
|
|
17
|
+
// confirmation (MemoriaViva UI #211) before materialising the destructive envelopes.
|
|
18
|
+
import { validateMemoryForget } from "@oscharko-dev/keiko-contracts/memory";
|
|
19
|
+
import { GovernanceError } from "./errors.js";
|
|
20
|
+
// ─── Scope coordinate equality ────────────────────────────────────────────────
|
|
21
|
+
// Pure: two scopes match when their discriminator AND coordinate field match exactly.
|
|
22
|
+
// Implemented via a canonical "kind:coordinate" string projection to collapse the
|
|
23
|
+
// per-kind branching (memory pattern from issue #205 scopeCoordinateKey).
|
|
24
|
+
function scopeCoordinateKey(scope) {
|
|
25
|
+
switch (scope.kind) {
|
|
26
|
+
case "user":
|
|
27
|
+
return `user:${scope.userId}`;
|
|
28
|
+
case "workspace":
|
|
29
|
+
return `workspace:${scope.workspaceId}`;
|
|
30
|
+
case "project":
|
|
31
|
+
return `project:${scope.projectId}`;
|
|
32
|
+
case "workflow":
|
|
33
|
+
return `workflow:${scope.workflowDefinitionId}`;
|
|
34
|
+
case "global":
|
|
35
|
+
return "global:";
|
|
36
|
+
default: {
|
|
37
|
+
const _exhaustive = scope;
|
|
38
|
+
void _exhaustive;
|
|
39
|
+
return "unknown:";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function scopeEquals(a, b) {
|
|
44
|
+
return scopeCoordinateKey(a) === scopeCoordinateKey(b);
|
|
45
|
+
}
|
|
46
|
+
// ─── Per-selector filter predicates ───────────────────────────────────────────
|
|
47
|
+
function matchById(record, selector) {
|
|
48
|
+
return record.id === selector.memoryId;
|
|
49
|
+
}
|
|
50
|
+
function matchByScope(record, selector) {
|
|
51
|
+
return scopeEquals(record.scope, selector.scope);
|
|
52
|
+
}
|
|
53
|
+
function matchByType(record, selector) {
|
|
54
|
+
return scopeEquals(record.scope, selector.scope) && record.type === selector.type;
|
|
55
|
+
}
|
|
56
|
+
function matchBySourceConversation(record, selector) {
|
|
57
|
+
if (!scopeEquals(record.scope, selector.scope))
|
|
58
|
+
return false;
|
|
59
|
+
return record.provenance.sourceConversationId === selector.sourceConversationId;
|
|
60
|
+
}
|
|
61
|
+
function matchByTimeWindow(record, selector, nowMs) {
|
|
62
|
+
if (!scopeEquals(record.scope, selector.scope))
|
|
63
|
+
return false;
|
|
64
|
+
return record.createdAt <= nowMs - selector.olderThanMs;
|
|
65
|
+
}
|
|
66
|
+
function assertSelectorWellFormed(selector) {
|
|
67
|
+
if (selector.kind === "by-time-window") {
|
|
68
|
+
if (!Number.isFinite(selector.olderThanMs) || selector.olderThanMs < 0) {
|
|
69
|
+
throw new GovernanceError("invalid-selector-input", "by-time-window olderThanMs must be a finite non-negative number");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function applySelector(record, selector, nowMs) {
|
|
74
|
+
switch (selector.kind) {
|
|
75
|
+
case "by-id":
|
|
76
|
+
return matchById(record, selector);
|
|
77
|
+
case "by-scope":
|
|
78
|
+
return matchByScope(record, selector);
|
|
79
|
+
case "by-type":
|
|
80
|
+
return matchByType(record, selector);
|
|
81
|
+
case "by-source-conversation":
|
|
82
|
+
return matchBySourceConversation(record, selector);
|
|
83
|
+
case "by-time-window":
|
|
84
|
+
return matchByTimeWindow(record, selector, nowMs);
|
|
85
|
+
default: {
|
|
86
|
+
// Exhaustiveness gate: a future widening of ForgetSelector surfaces here at
|
|
87
|
+
// compile time and at runtime as an unsupported-selector error.
|
|
88
|
+
const _exhaustive = selector;
|
|
89
|
+
void _exhaustive;
|
|
90
|
+
throw new GovernanceError("unsupported-selector", "unknown ForgetSelector kind");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ─── Selection ────────────────────────────────────────────────────────────────
|
|
95
|
+
export function selectMemoriesForForget(memories, selector, options) {
|
|
96
|
+
assertSelectorWellFormed(selector);
|
|
97
|
+
const protectPinned = options.protectPinned ?? true;
|
|
98
|
+
const protectArchived = options.protectArchived ?? false;
|
|
99
|
+
const selected = [];
|
|
100
|
+
for (const record of memories) {
|
|
101
|
+
if (protectPinned && record.pinned)
|
|
102
|
+
continue;
|
|
103
|
+
if (protectArchived && record.status === "archived")
|
|
104
|
+
continue;
|
|
105
|
+
if (record.status === "forgotten")
|
|
106
|
+
continue;
|
|
107
|
+
if (!applySelector(record, selector, options.nowMs))
|
|
108
|
+
continue;
|
|
109
|
+
selected.push(record);
|
|
110
|
+
}
|
|
111
|
+
return selected;
|
|
112
|
+
}
|
|
113
|
+
// ─── Envelope construction ────────────────────────────────────────────────────
|
|
114
|
+
const DEFAULT_FORGET_REASON = "user-requested forget";
|
|
115
|
+
function buildForgetEnvelope(record, context, options) {
|
|
116
|
+
const env = {
|
|
117
|
+
schemaVersion: "1",
|
|
118
|
+
memoryId: record.id,
|
|
119
|
+
reviewerId: context.reviewerId,
|
|
120
|
+
forgottenAt: context.nowMs,
|
|
121
|
+
reason: options.reason ?? DEFAULT_FORGET_REASON,
|
|
122
|
+
userAcknowledgedDestructive: true,
|
|
123
|
+
};
|
|
124
|
+
const v = validateMemoryForget(env);
|
|
125
|
+
if (!v.ok) {
|
|
126
|
+
throw new GovernanceError("envelope-validation-failed", `forget envelope failed contracts validation for memory ${record.id}`, v.errors);
|
|
127
|
+
}
|
|
128
|
+
return env;
|
|
129
|
+
}
|
|
130
|
+
export function buildForgetOperations(memories, context, options) {
|
|
131
|
+
// `writeTombstone` is currently observed only at the storage seam (vault #206 writes
|
|
132
|
+
// an audit tombstone unconditionally). The flag remains on the option bundle as a
|
|
133
|
+
// future-extension surface and as a forcing function for caller intent — the BFF
|
|
134
|
+
// route handler MUST decide consciously whether the destructive operation is
|
|
135
|
+
// tombstone-yielding.
|
|
136
|
+
void options.writeTombstone;
|
|
137
|
+
const envelopes = [];
|
|
138
|
+
for (const record of memories) {
|
|
139
|
+
envelopes.push(buildForgetEnvelope(record, context, options));
|
|
140
|
+
}
|
|
141
|
+
return envelopes;
|
|
142
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { KEIKO_MEMORY_GOVERNANCE_VERSION } from "./version.js";
|
|
2
|
+
export type { BuildForgetOperationsOptions, ConflictPair, ConflictReason, ConflictResolution, ForgetSelector, ForgetSelectorKind, GovernanceContext, SelectMemoriesForForgetOptions, StatusTransition, } from "./types.js";
|
|
3
|
+
export { FORGET_SELECTOR_KINDS } from "./types.js";
|
|
4
|
+
export { GovernanceError, type GovernanceErrorCode } from "./errors.js";
|
|
5
|
+
export { buildCorrection, type BuildCorrectionInput, type CorrectionEnvelopes, } from "./correction.js";
|
|
6
|
+
export { buildConflictTransitions, type ConflictTransitionResult, detectConflictPair, } from "./conflict.js";
|
|
7
|
+
export { buildForgetOperations, selectMemoriesForForget } from "./forget.js";
|
|
8
|
+
export { buildExpirationUpdate, supersededValidity } from "./retention.js";
|
|
9
|
+
export { buildArchiveOperation, buildPinOperation, buildUnpinOperation } from "./status-ops.js";
|
|
10
|
+
export { isMemorySuppressedFromRetrieval, type SuppressionOptions, type SuppressionReason, type SuppressionResult, } from "./suppression.js";
|
|
11
|
+
export { effectiveStrength, planMemoryMaintenance, MEMORY_MAINTENANCE_DEFAULTS, type MemoryAccessStatLike, type MemoryMaintenancePlan, type MemoryMaintenancePolicy, type PlanMaintenanceOptions, } from "./maintenance.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAG/D,YAAY,EACV,4BAA4B,EAC5B,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,8BAA8B,EAC9B,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGnD,OAAO,EAAE,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGxE,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,wBAAwB,EACxB,KAAK,wBAAwB,EAC7B,kBAAkB,GACnB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAG7E,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG3E,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAGhG,OAAO,EACL,+BAA+B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,GAC5B,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Public surface of @oscharko-dev/keiko-memory-governance (Epic #204 child #209).
|
|
2
|
+
// Keeping this file the SOLE entry point prevents downstream packages from reaching into
|
|
3
|
+
// private modules (ADR-0019 trust rule 7). Internal modules are package-private.
|
|
4
|
+
//
|
|
5
|
+
// Every function in this barrel is pure: same input + same `GovernanceContext` =>
|
|
6
|
+
// byte-identical output. The package never reads a clock, never invokes randomness, never
|
|
7
|
+
// touches the filesystem. The caller supplies `nowMs` and a `reviewerId` through
|
|
8
|
+
// `GovernanceContext`. Every returned envelope is REVALIDATED through the
|
|
9
|
+
// `@oscharko-dev/keiko-contracts` validators before being returned; a construction bug
|
|
10
|
+
// surfaces as a `GovernanceError("envelope-validation-failed", …)` rather than letting an
|
|
11
|
+
// invalid envelope cross the API boundary.
|
|
12
|
+
export { KEIKO_MEMORY_GOVERNANCE_VERSION } from "./version.js";
|
|
13
|
+
export { FORGET_SELECTOR_KINDS } from "./types.js";
|
|
14
|
+
// ─── Errors ──────────────────────────────────────────────────────────────────
|
|
15
|
+
export { GovernanceError } from "./errors.js";
|
|
16
|
+
// ─── Correction ──────────────────────────────────────────────────────────────
|
|
17
|
+
export { buildCorrection, } from "./correction.js";
|
|
18
|
+
// ─── Conflict ────────────────────────────────────────────────────────────────
|
|
19
|
+
export { buildConflictTransitions, detectConflictPair, } from "./conflict.js";
|
|
20
|
+
// ─── Forget ──────────────────────────────────────────────────────────────────
|
|
21
|
+
export { buildForgetOperations, selectMemoriesForForget } from "./forget.js";
|
|
22
|
+
// ─── Retention ───────────────────────────────────────────────────────────────
|
|
23
|
+
export { buildExpirationUpdate, supersededValidity } from "./retention.js";
|
|
24
|
+
// ─── Pin / unpin / archive ───────────────────────────────────────────────────
|
|
25
|
+
export { buildArchiveOperation, buildPinOperation, buildUnpinOperation } from "./status-ops.js";
|
|
26
|
+
// ─── Retrieval suppression ───────────────────────────────────────────────────
|
|
27
|
+
export { isMemorySuppressedFromRetrieval, } from "./suppression.js";
|
|
28
|
+
// ─── Maintenance planner (#204) ──────────────────────────────────────────────
|
|
29
|
+
export { effectiveStrength, planMemoryMaintenance, MEMORY_MAINTENANCE_DEFAULTS, } from "./maintenance.js";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MemoryId, MemoryRecord } from "@oscharko-dev/keiko-contracts/memory";
|
|
2
|
+
export interface MemoryAccessStatLike {
|
|
3
|
+
readonly lastAccessedAt: number;
|
|
4
|
+
readonly accessCount: number;
|
|
5
|
+
readonly outcomeCount?: number;
|
|
6
|
+
readonly utilitySum?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface MemoryMaintenancePolicy {
|
|
9
|
+
readonly halfLifeMs: number;
|
|
10
|
+
readonly promoteStrength: number;
|
|
11
|
+
readonly archiveMaxStrength: number;
|
|
12
|
+
readonly archiveMinAgeMs: number;
|
|
13
|
+
readonly forgetArchivedMinAgeMs: number;
|
|
14
|
+
readonly forgetProposedMaxStrength: number;
|
|
15
|
+
readonly forgetProposedMinAgeMs: number;
|
|
16
|
+
readonly maxForgetPerRun: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const MEMORY_MAINTENANCE_DEFAULTS: MemoryMaintenancePolicy;
|
|
19
|
+
export interface MemoryMaintenancePlan {
|
|
20
|
+
readonly promote: MemoryId[];
|
|
21
|
+
readonly archive: MemoryId[];
|
|
22
|
+
readonly forget: {
|
|
23
|
+
id: MemoryId;
|
|
24
|
+
reason: string;
|
|
25
|
+
}[];
|
|
26
|
+
}
|
|
27
|
+
export interface PlanMaintenanceOptions {
|
|
28
|
+
readonly nowMs: number;
|
|
29
|
+
readonly policy?: Partial<MemoryMaintenancePolicy>;
|
|
30
|
+
}
|
|
31
|
+
export declare function effectiveStrength(record: MemoryRecord, stat: MemoryAccessStatLike | undefined, nowMs: number, halfLifeMs?: number): number;
|
|
32
|
+
export declare function planMemoryMaintenance(records: readonly MemoryRecord[], accessStats: ReadonlyMap<MemoryId, MemoryAccessStatLike>, options: PlanMaintenanceOptions): MemoryMaintenancePlan;
|
|
33
|
+
//# sourceMappingURL=maintenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maintenance.d.ts","sourceRoot":"","sources":["../src/maintenance.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAInF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAI7B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC;IAC3C,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAID,eAAO,MAAM,2BAA2B,EAAE,uBASzC,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,QAAQ,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACrD;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACpD;AAgCD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,oBAAoB,GAAG,SAAS,EACtC,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,MAA+C,GAC1D,MAAM,CAMR;AAsID,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,WAAW,EAAE,WAAW,CAAC,QAAQ,EAAE,oBAAoB,CAAC,EACxD,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAiBvB"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Memory maintenance planner (#204) — the "consolidate + forget" decision engine.
|
|
2
|
+
//
|
|
3
|
+
// PURE: same input + same nowMs => byte-identical plan. No clock reads, no IO, no randomness. The
|
|
4
|
+
// caller (BFF maintenance orchestrator) pre-fetches the records and the access stats, calls this to
|
|
5
|
+
// compute a plan, and applies the plan back to the vault + audit ledger. The split mirrors the
|
|
6
|
+
// consolidation engine: planning is a pure function, application is the impure caller's job.
|
|
7
|
+
//
|
|
8
|
+
// Each record receives AT MOST ONE decision. Priority (highest first): forget > archive > promote.
|
|
9
|
+
// A pinned record is never decayed, archived, or forgotten (its strength is pinned to 1); it may
|
|
10
|
+
// still be promoted since that only strengthens it.
|
|
11
|
+
//
|
|
12
|
+
// Strength model (human-memory analogue):
|
|
13
|
+
// base = provenance.confidence (calibrated [0,1])
|
|
14
|
+
// freqBoost = 1 + 0.15 * ln(1 + accessCount) (recall strengthens)
|
|
15
|
+
// recencyFactor= exp(-ln2 * (now - lastTouch) / HALF_LIFE) (disuse decays; 45-day half-life)
|
|
16
|
+
// utilityFactor= 0.5 + meanOutcomeUtility (outcome-gated; [0.5,1.5], default 1)
|
|
17
|
+
// strength = pinned ? 1 : clamp(base * freqBoost * recencyFactor * utilityFactor, 0, 1)
|
|
18
|
+
// lastTouch is the last access timestamp, falling back to createdAt when never accessed.
|
|
19
|
+
//
|
|
20
|
+
// OUTCOME-DRIVEN FORGETTING (#204, O-V1). Disuse is not the only reason to forget: a memory can be
|
|
21
|
+
// recent and frequently recalled yet keep leading to the WRONG answer. `utilityFactor` folds the
|
|
22
|
+
// mean of a memory's governed retention outcomes (proposal accepted/rejected, conflict won/lost,
|
|
23
|
+
// accepted correction superseding its origin) into the strength: all-bad outcomes (mean 0) halve it
|
|
24
|
+
// so the memory archives/forgets sooner; all-good (mean 1) raise it 1.5x so a proven-useful memory
|
|
25
|
+
// resists disuse decay. With NO outcomes the factor is exactly 1 and the model is byte-identical to
|
|
26
|
+
// the pre-O-V1 curve. The factor is bounded so outcomes shift, but never dominate, the prior signal.
|
|
27
|
+
//
|
|
28
|
+
// CONFIDENCE IS IMMUTABLE PROVENANCE (#204, O-V2). This pass NEVER overwrites provenance.confidence.
|
|
29
|
+
// Confidence is the calibrated veridicality of a memory at capture time and is changed only by an
|
|
30
|
+
// explicit, governed user correction/edit — never by a background job. "Reinforcement" and "decay"
|
|
31
|
+
// are not persisted nudges to confidence (that conflated veridicality with activation, lost the
|
|
32
|
+
// original value, and compounded non-idempotently as 0.6^n). Instead:
|
|
33
|
+
// - reinforcement-on-reuse is realised LIVE at retrieval time via the access-derived strength
|
|
34
|
+
// subscore (keiko-memory-retrieval strength.ts), and
|
|
35
|
+
// - disuse-decay is computed ON THE FLY here through `recencyFactor` inside `effectiveStrength`,
|
|
36
|
+
// which already gates archive/forget. So a faded memory still archives/forgets, but its
|
|
37
|
+
// provenance stays intact and every run is idempotent.
|
|
38
|
+
const DAY_MS = 864e5;
|
|
39
|
+
export const MEMORY_MAINTENANCE_DEFAULTS = {
|
|
40
|
+
halfLifeMs: 45 * DAY_MS,
|
|
41
|
+
promoteStrength: 0.45,
|
|
42
|
+
archiveMaxStrength: 0.2,
|
|
43
|
+
archiveMinAgeMs: 3 * DAY_MS,
|
|
44
|
+
forgetArchivedMinAgeMs: 30 * DAY_MS,
|
|
45
|
+
forgetProposedMaxStrength: 0.1,
|
|
46
|
+
forgetProposedMinAgeMs: 14 * DAY_MS,
|
|
47
|
+
maxForgetPerRun: 25,
|
|
48
|
+
};
|
|
49
|
+
function clamp01(n) {
|
|
50
|
+
if (n < 0)
|
|
51
|
+
return 0;
|
|
52
|
+
if (n > 1)
|
|
53
|
+
return 1;
|
|
54
|
+
return n;
|
|
55
|
+
}
|
|
56
|
+
function recencyFactorOf(record, stat, nowMs, halfLifeMs) {
|
|
57
|
+
// Only a genuine recall advances the recency anchor. An outcome-only row (accessCount 0, written
|
|
58
|
+
// by recordOutcome before the memory was ever recalled) carries no "last use", so recency falls
|
|
59
|
+
// back to createdAt — exactly as a never-tracked memory does. For every real access row
|
|
60
|
+
// (accessCount >= 1) this is byte-identical to the prior `stat.lastAccessedAt ?? createdAt`.
|
|
61
|
+
const lastTouch = stat !== undefined && stat.accessCount > 0 ? stat.lastAccessedAt : record.createdAt;
|
|
62
|
+
return Math.exp((-Math.LN2 * (nowMs - lastTouch)) / halfLifeMs);
|
|
63
|
+
}
|
|
64
|
+
// Outcome-gated utility factor (#204, O-V1). Mean utility of the memory's recorded retention
|
|
65
|
+
// outcomes mapped linearly onto [0.5, 1.5]; no outcomes => exactly 1 (strength model unchanged).
|
|
66
|
+
function utilityFactor(stat) {
|
|
67
|
+
const count = stat?.outcomeCount ?? 0;
|
|
68
|
+
if (count <= 0)
|
|
69
|
+
return 1;
|
|
70
|
+
const meanUtility = clamp01((stat?.utilitySum ?? 0) / count);
|
|
71
|
+
return 0.5 + meanUtility;
|
|
72
|
+
}
|
|
73
|
+
export function effectiveStrength(record, stat, nowMs, halfLifeMs = MEMORY_MAINTENANCE_DEFAULTS.halfLifeMs) {
|
|
74
|
+
if (record.pinned)
|
|
75
|
+
return 1;
|
|
76
|
+
const base = record.provenance.confidence;
|
|
77
|
+
const freqBoost = 1 + 0.15 * Math.log1p(stat?.accessCount ?? 0);
|
|
78
|
+
const recencyFactor = recencyFactorOf(record, stat, nowMs, halfLifeMs);
|
|
79
|
+
return clamp01(base * freqBoost * recencyFactor * utilityFactor(stat));
|
|
80
|
+
}
|
|
81
|
+
function isValidityExpired(record, nowMs) {
|
|
82
|
+
const until = record.validity.validUntil;
|
|
83
|
+
return until !== undefined && until <= nowMs;
|
|
84
|
+
}
|
|
85
|
+
function shouldForget(c, p, nowMs) {
|
|
86
|
+
if (isValidityExpired(c.record, nowMs))
|
|
87
|
+
return "validity-expired";
|
|
88
|
+
// Multi-condition prune guard (#204, O-V5). Hard-deleting an archived memory requires it to be
|
|
89
|
+
// BOTH aged out AND genuinely faint — never age alone. The archive ROUTE accepts any memory
|
|
90
|
+
// regardless of strength (a user can deliberately archive a high-confidence record to
|
|
91
|
+
// de-prioritise it), so age-only pruning would silently delete a still-valuable, explicitly-kept
|
|
92
|
+
// memory 30 days on. Reusing `archiveMaxStrength` as the faintness floor keeps the policy
|
|
93
|
+
// symmetric: a record is pruned only once it is at least as faint as the bar that would archive
|
|
94
|
+
// it. Archive (de-prioritise) is not consent to delete; only disuse is.
|
|
95
|
+
if (c.record.status === "archived" &&
|
|
96
|
+
c.ageMs > p.forgetArchivedMinAgeMs &&
|
|
97
|
+
c.strength < p.archiveMaxStrength) {
|
|
98
|
+
return "archived-aged-out";
|
|
99
|
+
}
|
|
100
|
+
if (c.record.status === "proposed" &&
|
|
101
|
+
c.strength < p.forgetProposedMaxStrength &&
|
|
102
|
+
c.accessCount === 0 &&
|
|
103
|
+
c.ageMs > p.forgetProposedMinAgeMs) {
|
|
104
|
+
return "proposed-faint-aged-out";
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
function shouldArchive(c, p) {
|
|
109
|
+
return (c.record.status === "accepted" &&
|
|
110
|
+
c.strength < p.archiveMaxStrength &&
|
|
111
|
+
c.ageMs > p.archiveMinAgeMs);
|
|
112
|
+
}
|
|
113
|
+
function shouldPromote(c, p) {
|
|
114
|
+
return (c.record.status === "proposed" &&
|
|
115
|
+
c.record.provenance.sensitivity === "public" &&
|
|
116
|
+
c.strength >= p.promoteStrength);
|
|
117
|
+
}
|
|
118
|
+
function decideForLive(c, p, nowMs) {
|
|
119
|
+
if (!c.record.pinned) {
|
|
120
|
+
const forgetReason = shouldForget(c, p, nowMs);
|
|
121
|
+
if (forgetReason !== null)
|
|
122
|
+
return { kind: "forget", reason: forgetReason };
|
|
123
|
+
if (shouldArchive(c, p))
|
|
124
|
+
return { kind: "archive" };
|
|
125
|
+
}
|
|
126
|
+
if (shouldPromote(c, p))
|
|
127
|
+
return { kind: "promote" };
|
|
128
|
+
return { kind: "none" };
|
|
129
|
+
}
|
|
130
|
+
function buildContext(record, stat, nowMs, policy) {
|
|
131
|
+
return {
|
|
132
|
+
record,
|
|
133
|
+
stat,
|
|
134
|
+
strength: effectiveStrength(record, stat, nowMs, policy.halfLifeMs),
|
|
135
|
+
ageMs: nowMs - record.createdAt,
|
|
136
|
+
accessCount: stat?.accessCount ?? 0,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function applyDecision(acc, c, decision) {
|
|
140
|
+
const id = c.record.id;
|
|
141
|
+
switch (decision.kind) {
|
|
142
|
+
case "forget":
|
|
143
|
+
acc.forgetCandidates.push({ id, reason: decision.reason ?? "forget", strength: c.strength });
|
|
144
|
+
return;
|
|
145
|
+
case "archive":
|
|
146
|
+
acc.archive.push(id);
|
|
147
|
+
return;
|
|
148
|
+
case "promote":
|
|
149
|
+
acc.promote.push(id);
|
|
150
|
+
return;
|
|
151
|
+
case "none":
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Forget is bounded per run and ordered by ascending strength so the faintest memories go first.
|
|
156
|
+
// Ties break on id for determinism.
|
|
157
|
+
function boundForget(candidates, maxForgetPerRun) {
|
|
158
|
+
return [...candidates]
|
|
159
|
+
.sort((a, b) => a.strength !== b.strength ? a.strength - b.strength : a.id.localeCompare(b.id))
|
|
160
|
+
.slice(0, maxForgetPerRun)
|
|
161
|
+
.map((c) => ({ id: c.id, reason: c.reason }));
|
|
162
|
+
}
|
|
163
|
+
export function planMemoryMaintenance(records, accessStats, options) {
|
|
164
|
+
const policy = { ...MEMORY_MAINTENANCE_DEFAULTS, ...options.policy };
|
|
165
|
+
const acc = {
|
|
166
|
+
promote: [],
|
|
167
|
+
archive: [],
|
|
168
|
+
forgetCandidates: [],
|
|
169
|
+
};
|
|
170
|
+
for (const record of records) {
|
|
171
|
+
const stat = accessStats.get(record.id);
|
|
172
|
+
const ctx = buildContext(record, stat, options.nowMs, policy);
|
|
173
|
+
applyDecision(acc, ctx, decideForLive(ctx, policy, options.nowMs));
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
promote: acc.promote,
|
|
177
|
+
archive: acc.archive,
|
|
178
|
+
forget: boundForget(acc.forgetCandidates, policy.maxForgetPerRun),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MemoryRecord, MemoryUpdate, MemoryValidityInterval } from "@oscharko-dev/keiko-contracts/memory";
|
|
2
|
+
import type { GovernanceContext } from "./types.js";
|
|
3
|
+
export declare function supersededValidity(record: MemoryRecord, nowMs: number): MemoryValidityInterval | null;
|
|
4
|
+
export declare function buildExpirationUpdate(memory: MemoryRecord, newValidUntilMs: number, context: GovernanceContext): MemoryUpdate;
|
|
5
|
+
//# sourceMappingURL=retention.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../src/retention.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACvB,MAAM,sCAAsC,CAAC;AAI9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AASpD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,GACZ,sBAAsB,GAAG,IAAI,CAK/B;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EACpB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,iBAAiB,GACzB,YAAY,CAiCd"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Expiration / retention update builder.
|
|
2
|
+
//
|
|
3
|
+
// buildExpirationUpdate produces a MemoryUpdate envelope that patches ONLY the validity
|
|
4
|
+
// interval. The semantics are:
|
|
5
|
+
// - validFrom is preserved from the existing record (an expiration update never
|
|
6
|
+
// rewrites the underlying fact's start time).
|
|
7
|
+
// - validUntil is set to the new value supplied by the caller.
|
|
8
|
+
//
|
|
9
|
+
// Throws GovernanceError('invalid-validity-window') if newValidUntilMs <= validFrom,
|
|
10
|
+
// because a zero-or-negative-duration validity is structurally meaningless and would
|
|
11
|
+
// also fail the contracts validateMemoryValidityInterval rule downstream.
|
|
12
|
+
import { validateMemoryUpdate } from "@oscharko-dev/keiko-contracts/memory";
|
|
13
|
+
import { GovernanceError } from "./errors.js";
|
|
14
|
+
// Bi-temporal-lite (#204, C1): the validity interval for a record being SUPERSEDED at `nowMs` — its
|
|
15
|
+
// belief window CLOSED at the moment its replacement takes over. With this, "what did we believe as
|
|
16
|
+
// of date T" is answerable (validFrom <= T AND (validUntil IS NULL OR validUntil > T)) and a closed
|
|
17
|
+
// window drops out of default retrieval automatically. Returns null when closing would not form a
|
|
18
|
+
// valid interval (nowMs <= validFrom) or would EXTEND an already-closed window (existing validUntil
|
|
19
|
+
// <= nowMs), so an already-expired fact is never silently un-expired. Pure; no validation throw —
|
|
20
|
+
// the caller applies it as an additive patch alongside the status transition.
|
|
21
|
+
export function supersededValidity(record, nowMs) {
|
|
22
|
+
const { validFrom, validUntil } = record.validity;
|
|
23
|
+
if (!Number.isFinite(nowMs) || nowMs <= validFrom)
|
|
24
|
+
return null;
|
|
25
|
+
if (validUntil !== undefined && validUntil <= nowMs)
|
|
26
|
+
return null;
|
|
27
|
+
return { validFrom, validUntil: nowMs };
|
|
28
|
+
}
|
|
29
|
+
export function buildExpirationUpdate(memory, newValidUntilMs, context) {
|
|
30
|
+
if (!Number.isFinite(newValidUntilMs)) {
|
|
31
|
+
throw new GovernanceError("invalid-validity-window", "newValidUntilMs must be a finite number");
|
|
32
|
+
}
|
|
33
|
+
if (newValidUntilMs <= memory.validity.validFrom) {
|
|
34
|
+
throw new GovernanceError("invalid-validity-window", "newValidUntilMs must be strictly greater than memory.validity.validFrom", [
|
|
35
|
+
`validFrom: ${String(memory.validity.validFrom)}`,
|
|
36
|
+
`newValidUntilMs: ${String(newValidUntilMs)}`,
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
const update = {
|
|
40
|
+
schemaVersion: "1",
|
|
41
|
+
memoryId: memory.id,
|
|
42
|
+
reviewerId: context.reviewerId,
|
|
43
|
+
updatedAt: context.nowMs,
|
|
44
|
+
validityPatch: {
|
|
45
|
+
validFrom: memory.validity.validFrom,
|
|
46
|
+
validUntil: newValidUntilMs,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const v = validateMemoryUpdate(update);
|
|
50
|
+
if (!v.ok) {
|
|
51
|
+
throw new GovernanceError("envelope-validation-failed", "expiration update failed contracts validation", v.errors);
|
|
52
|
+
}
|
|
53
|
+
return update;
|
|
54
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MemoryArchive, MemoryPin, MemoryRecord, MemoryUnpin } from "@oscharko-dev/keiko-contracts/memory";
|
|
2
|
+
import type { GovernanceContext } from "./types.js";
|
|
3
|
+
export declare function buildPinOperation(memory: MemoryRecord, context: GovernanceContext, reason?: string): MemoryPin;
|
|
4
|
+
export declare function buildUnpinOperation(memory: MemoryRecord, context: GovernanceContext, reason?: string): MemoryUnpin;
|
|
5
|
+
export declare function buildArchiveOperation(memory: MemoryRecord, context: GovernanceContext, reason?: string): MemoryArchive;
|
|
6
|
+
//# sourceMappingURL=status-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-ops.d.ts","sourceRoot":"","sources":["../src/status-ops.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,YAAY,EACZ,WAAW,EACZ,MAAM,sCAAsC,CAAC;AAS9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGpD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,MAAM,GACd,SAAS,CAsBX;AAGD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,CAsBb;AA0BD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,iBAAiB,EAC1B,MAAM,CAAC,EAAE,MAAM,GACd,aAAa,CAkBf"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Pin / unpin / archive envelope builders.
|
|
2
|
+
//
|
|
3
|
+
// Each builder is idempotency-rejecting: pinning an already-pinned memory throws
|
|
4
|
+
// GovernanceError("idempotent-noop"), and likewise for unpin/archive. The check happens
|
|
5
|
+
// BEFORE the envelope is constructed so the caller never receives a "valid" envelope
|
|
6
|
+
// representing a no-op. Every emitted envelope is revalidated through the contracts
|
|
7
|
+
// validator (validateMemoryPin / validateMemoryUnpin / validateMemoryArchive) as a
|
|
8
|
+
// defence-in-depth guard against future contract drift.
|
|
9
|
+
//
|
|
10
|
+
// Archive additionally rejects memories whose current status forbids the transition to
|
|
11
|
+
// archived (per MEMORY_STATUS_TRANSITIONS: archive is legal from accepted, superseded,
|
|
12
|
+
// conflicted, expired — and ILLEGAL from proposed, rejected, archived, forgotten). The
|
|
13
|
+
// check is delegated to the same checkStatusTransition helper the conflict layer uses so
|
|
14
|
+
// the two layers agree on transition legality.
|
|
15
|
+
import { checkStatusTransition, validateMemoryArchive, validateMemoryPin, validateMemoryUnpin, } from "@oscharko-dev/keiko-contracts/memory";
|
|
16
|
+
import { GovernanceError } from "./errors.js";
|
|
17
|
+
// ─── Pin ──────────────────────────────────────────────────────────────────────
|
|
18
|
+
export function buildPinOperation(memory, context, reason) {
|
|
19
|
+
if (memory.pinned) {
|
|
20
|
+
throw new GovernanceError("idempotent-noop", `memory ${memory.id} is already pinned`, [
|
|
21
|
+
`memoryId: ${memory.id}`,
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
const env = {
|
|
25
|
+
schemaVersion: "1",
|
|
26
|
+
memoryId: memory.id,
|
|
27
|
+
reviewerId: context.reviewerId,
|
|
28
|
+
pinnedAt: context.nowMs,
|
|
29
|
+
...(reason !== undefined ? { reason } : {}),
|
|
30
|
+
};
|
|
31
|
+
const v = validateMemoryPin(env);
|
|
32
|
+
if (!v.ok) {
|
|
33
|
+
throw new GovernanceError("envelope-validation-failed", "pin envelope failed contracts validation", v.errors);
|
|
34
|
+
}
|
|
35
|
+
return env;
|
|
36
|
+
}
|
|
37
|
+
// ─── Unpin ────────────────────────────────────────────────────────────────────
|
|
38
|
+
export function buildUnpinOperation(memory, context, reason) {
|
|
39
|
+
if (!memory.pinned) {
|
|
40
|
+
throw new GovernanceError("idempotent-noop", `memory ${memory.id} is not pinned`, [
|
|
41
|
+
`memoryId: ${memory.id}`,
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
const env = {
|
|
45
|
+
schemaVersion: "1",
|
|
46
|
+
memoryId: memory.id,
|
|
47
|
+
reviewerId: context.reviewerId,
|
|
48
|
+
unpinnedAt: context.nowMs,
|
|
49
|
+
...(reason !== undefined ? { reason } : {}),
|
|
50
|
+
};
|
|
51
|
+
const v = validateMemoryUnpin(env);
|
|
52
|
+
if (!v.ok) {
|
|
53
|
+
throw new GovernanceError("envelope-validation-failed", "unpin envelope failed contracts validation", v.errors);
|
|
54
|
+
}
|
|
55
|
+
return env;
|
|
56
|
+
}
|
|
57
|
+
// ─── Archive ──────────────────────────────────────────────────────────────────
|
|
58
|
+
function assertArchivable(memory) {
|
|
59
|
+
if (memory.status === "archived") {
|
|
60
|
+
throw new GovernanceError("idempotent-noop", `memory ${memory.id} is already archived`, [
|
|
61
|
+
`memoryId: ${memory.id}`,
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
if (memory.status === "forgotten") {
|
|
65
|
+
throw new GovernanceError("memory-not-eligible", `memory ${memory.id} is forgotten and cannot be archived`, [`memoryId: ${memory.id}`, `status: ${memory.status}`]);
|
|
66
|
+
}
|
|
67
|
+
const check = checkStatusTransition(memory.status, "archived");
|
|
68
|
+
if (!check.ok) {
|
|
69
|
+
throw new GovernanceError("illegal-status-transition", check.reason ?? `illegal transition: ${memory.status} -> archived`, [`memoryId: ${memory.id}`, `from: ${memory.status}`, `to: archived`]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function buildArchiveOperation(memory, context, reason) {
|
|
73
|
+
assertArchivable(memory);
|
|
74
|
+
const env = {
|
|
75
|
+
schemaVersion: "1",
|
|
76
|
+
memoryId: memory.id,
|
|
77
|
+
reviewerId: context.reviewerId,
|
|
78
|
+
archivedAt: context.nowMs,
|
|
79
|
+
...(reason !== undefined ? { reason } : {}),
|
|
80
|
+
};
|
|
81
|
+
const v = validateMemoryArchive(env);
|
|
82
|
+
if (!v.ok) {
|
|
83
|
+
throw new GovernanceError("envelope-validation-failed", "archive envelope failed contracts validation", v.errors);
|
|
84
|
+
}
|
|
85
|
+
return env;
|
|
86
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MemoryRecord } from "@oscharko-dev/keiko-contracts/memory";
|
|
2
|
+
export type SuppressionReason = "archived" | "forgotten" | "conflicted" | "expired" | "proposed" | "rejected" | "stale-low-confidence";
|
|
3
|
+
export interface SuppressionResult {
|
|
4
|
+
readonly suppressed: boolean;
|
|
5
|
+
readonly reason?: SuppressionReason;
|
|
6
|
+
}
|
|
7
|
+
export interface SuppressionOptions {
|
|
8
|
+
readonly staleConfidenceThreshold?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function isMemorySuppressedFromRetrieval(memory: MemoryRecord, nowMs: number, options?: SuppressionOptions): SuppressionResult;
|
|
11
|
+
//# sourceMappingURL=suppression.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suppression.d.ts","sourceRoot":"","sources":["../src/suppression.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,sCAAsC,CAAC;AAIvF,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,WAAW,GACX,YAAY,GACZ,SAAS,GACT,UAAU,GACV,UAAU,GACV,sBAAsB,CAAC;AAE3B,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CACrC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CAC5C;AA8DD,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CASnB"}
|