@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.
Files changed (148) hide show
  1. package/README.md +65 -0
  2. package/agent-forge.json +11 -0
  3. package/dist/capability.d.ts +182 -0
  4. package/dist/capability.d.ts.map +1 -0
  5. package/dist/capability.js +2565 -0
  6. package/dist/capability.js.map +1 -0
  7. package/dist/entry.d.ts +36 -0
  8. package/dist/entry.d.ts.map +1 -0
  9. package/dist/entry.js +154 -0
  10. package/dist/entry.js.map +1 -0
  11. package/dist/index.d.ts +49 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +49 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/memory/assistant-card.d.ts +31 -0
  16. package/dist/memory/assistant-card.d.ts.map +1 -0
  17. package/dist/memory/assistant-card.js +108 -0
  18. package/dist/memory/assistant-card.js.map +1 -0
  19. package/dist/memory/candidates.d.ts +65 -0
  20. package/dist/memory/candidates.d.ts.map +1 -0
  21. package/dist/memory/candidates.js +100 -0
  22. package/dist/memory/candidates.js.map +1 -0
  23. package/dist/memory/code-memory.d.ts +89 -0
  24. package/dist/memory/code-memory.d.ts.map +1 -0
  25. package/dist/memory/code-memory.js +104 -0
  26. package/dist/memory/code-memory.js.map +1 -0
  27. package/dist/memory/compaction-sequencer.d.ts +63 -0
  28. package/dist/memory/compaction-sequencer.d.ts.map +1 -0
  29. package/dist/memory/compaction-sequencer.js +129 -0
  30. package/dist/memory/compaction-sequencer.js.map +1 -0
  31. package/dist/memory/continuation.d.ts +44 -0
  32. package/dist/memory/continuation.d.ts.map +1 -0
  33. package/dist/memory/continuation.js +49 -0
  34. package/dist/memory/continuation.js.map +1 -0
  35. package/dist/memory/curation.d.ts +58 -0
  36. package/dist/memory/curation.d.ts.map +1 -0
  37. package/dist/memory/curation.js +68 -0
  38. package/dist/memory/curation.js.map +1 -0
  39. package/dist/memory/egress-policy.d.ts +50 -0
  40. package/dist/memory/egress-policy.d.ts.map +1 -0
  41. package/dist/memory/egress-policy.js +71 -0
  42. package/dist/memory/egress-policy.js.map +1 -0
  43. package/dist/memory/embedding-provider.d.ts +70 -0
  44. package/dist/memory/embedding-provider.d.ts.map +1 -0
  45. package/dist/memory/embedding-provider.js +164 -0
  46. package/dist/memory/embedding-provider.js.map +1 -0
  47. package/dist/memory/embedding-reranker.d.ts +56 -0
  48. package/dist/memory/embedding-reranker.d.ts.map +1 -0
  49. package/dist/memory/embedding-reranker.js +109 -0
  50. package/dist/memory/embedding-reranker.js.map +1 -0
  51. package/dist/memory/foundation.d.ts +168 -0
  52. package/dist/memory/foundation.d.ts.map +1 -0
  53. package/dist/memory/foundation.js +487 -0
  54. package/dist/memory/foundation.js.map +1 -0
  55. package/dist/memory/host-module-import.d.ts +25 -0
  56. package/dist/memory/host-module-import.d.ts.map +1 -0
  57. package/dist/memory/host-module-import.js +41 -0
  58. package/dist/memory/host-module-import.js.map +1 -0
  59. package/dist/memory/ledger.d.ts +58 -0
  60. package/dist/memory/ledger.d.ts.map +1 -0
  61. package/dist/memory/ledger.js +315 -0
  62. package/dist/memory/ledger.js.map +1 -0
  63. package/dist/memory/lifecycle.d.ts +124 -0
  64. package/dist/memory/lifecycle.d.ts.map +1 -0
  65. package/dist/memory/lifecycle.js +201 -0
  66. package/dist/memory/lifecycle.js.map +1 -0
  67. package/dist/memory/memory-network.d.ts +55 -0
  68. package/dist/memory/memory-network.d.ts.map +1 -0
  69. package/dist/memory/memory-network.js +70 -0
  70. package/dist/memory/memory-network.js.map +1 -0
  71. package/dist/memory/model-cache-hygiene.d.ts +18 -0
  72. package/dist/memory/model-cache-hygiene.d.ts.map +1 -0
  73. package/dist/memory/model-cache-hygiene.js +38 -0
  74. package/dist/memory/model-cache-hygiene.js.map +1 -0
  75. package/dist/memory/preference-disambiguator.d.ts +43 -0
  76. package/dist/memory/preference-disambiguator.d.ts.map +1 -0
  77. package/dist/memory/preference-disambiguator.js +81 -0
  78. package/dist/memory/preference-disambiguator.js.map +1 -0
  79. package/dist/memory/preference-lifecycle.d.ts +66 -0
  80. package/dist/memory/preference-lifecycle.d.ts.map +1 -0
  81. package/dist/memory/preference-lifecycle.js +129 -0
  82. package/dist/memory/preference-lifecycle.js.map +1 -0
  83. package/dist/memory/preference-promotion.d.ts +87 -0
  84. package/dist/memory/preference-promotion.d.ts.map +1 -0
  85. package/dist/memory/preference-promotion.js +102 -0
  86. package/dist/memory/preference-promotion.js.map +1 -0
  87. package/dist/memory/preference-resolver.d.ts +44 -0
  88. package/dist/memory/preference-resolver.d.ts.map +1 -0
  89. package/dist/memory/preference-resolver.js +107 -0
  90. package/dist/memory/preference-resolver.js.map +1 -0
  91. package/dist/memory/purge-journal.d.ts +76 -0
  92. package/dist/memory/purge-journal.d.ts.map +1 -0
  93. package/dist/memory/purge-journal.js +130 -0
  94. package/dist/memory/purge-journal.js.map +1 -0
  95. package/dist/memory/purge.d.ts +90 -0
  96. package/dist/memory/purge.d.ts.map +1 -0
  97. package/dist/memory/purge.js +138 -0
  98. package/dist/memory/purge.js.map +1 -0
  99. package/dist/memory/recall-agent.d.ts +84 -0
  100. package/dist/memory/recall-agent.d.ts.map +1 -0
  101. package/dist/memory/recall-agent.js +199 -0
  102. package/dist/memory/recall-agent.js.map +1 -0
  103. package/dist/memory/recall-index.d.ts +87 -0
  104. package/dist/memory/recall-index.d.ts.map +1 -0
  105. package/dist/memory/recall-index.js +222 -0
  106. package/dist/memory/recall-index.js.map +1 -0
  107. package/dist/memory/recall-packet.d.ts +121 -0
  108. package/dist/memory/recall-packet.d.ts.map +1 -0
  109. package/dist/memory/recall-packet.js +156 -0
  110. package/dist/memory/recall-packet.js.map +1 -0
  111. package/dist/memory/scheduler-api.d.ts +99 -0
  112. package/dist/memory/scheduler-api.d.ts.map +1 -0
  113. package/dist/memory/scheduler-api.js +93 -0
  114. package/dist/memory/scheduler-api.js.map +1 -0
  115. package/dist/memory/scheduler.d.ts +55 -0
  116. package/dist/memory/scheduler.d.ts.map +1 -0
  117. package/dist/memory/scheduler.js +91 -0
  118. package/dist/memory/scheduler.js.map +1 -0
  119. package/dist/memory/store.d.ts +107 -0
  120. package/dist/memory/store.d.ts.map +1 -0
  121. package/dist/memory/store.js +208 -0
  122. package/dist/memory/store.js.map +1 -0
  123. package/dist/memory/suite-memory.d.ts +208 -0
  124. package/dist/memory/suite-memory.d.ts.map +1 -0
  125. package/dist/memory/suite-memory.js +288 -0
  126. package/dist/memory/suite-memory.js.map +1 -0
  127. package/dist/memory/transfer.d.ts +142 -0
  128. package/dist/memory/transfer.d.ts.map +1 -0
  129. package/dist/memory/transfer.js +210 -0
  130. package/dist/memory/transfer.js.map +1 -0
  131. package/dist/memory/vector-index.d.ts +39 -0
  132. package/dist/memory/vector-index.d.ts.map +1 -0
  133. package/dist/memory/vector-index.js +136 -0
  134. package/dist/memory/vector-index.js.map +1 -0
  135. package/dist/memory/write-budget.d.ts +33 -0
  136. package/dist/memory/write-budget.d.ts.map +1 -0
  137. package/dist/memory/write-budget.js +45 -0
  138. package/dist/memory/write-budget.js.map +1 -0
  139. package/dist/testing/memory-testkit.d.ts +149 -0
  140. package/dist/testing/memory-testkit.d.ts.map +1 -0
  141. package/dist/testing/memory-testkit.js +438 -0
  142. package/dist/testing/memory-testkit.js.map +1 -0
  143. package/dist/utils/sync-sleep.d.ts +2 -0
  144. package/dist/utils/sync-sleep.d.ts.map +1 -0
  145. package/dist/utils/sync-sleep.js +11 -0
  146. package/dist/utils/sync-sleep.js.map +1 -0
  147. package/package.json +56 -0
  148. package/plugin.json +10 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foundation.d.ts","sourceRoot":"","sources":["../../src/memory/foundation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — canonical atom, identity, and immutable snapshots (1C.1a).\n *\n * A canonical atom is the immutable committed fact. Derived state (attention,\n * effective status, purgeAt) is NOT part of the atom; it is rebuilt from\n * lifecycle events (1C.1b/1C.2). Everything returned to hosts and plugins is a\n * detached, deep-frozen snapshot: mutating the input after `buildMemoryAtomV1`\n * never changes the stored atom.\n *\n * Field semantics follow `docs/design/记忆系统设计.md` §4 (记录模型), which is the\n * authoritative source for memory fields.\n */\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\n\n/** Source of one memory: where the fact came from, optionally pinned by revision/digest. */\nexport interface MemorySourceRefV1 {\n\treadonly kind: \"session\" | \"entry\" | \"artifact\" | \"tool\" | \"state\";\n\treadonly id: string;\n\treadonly revision?: string;\n\treadonly digest?: string;\n}\n\n/** Namespace-scoped search facet. Facets are atom facts, never derived. */\nexport interface MemoryFacetV1 {\n\treadonly namespace: string;\n\treadonly schemaVersion: number;\n\treadonly key: string;\n\treadonly value: string;\n}\n\n/** Typed relation between atoms. Weights/validity belong to projections, not here. */\nexport interface MemoryRelationV1 {\n\treadonly relationId: string;\n\treadonly namespace: string;\n\treadonly schemaVersion: number;\n\treadonly kind: string;\n\treadonly targetMemoryId?: string;\n\treadonly targetRef?: MemorySourceRefV1;\n\treadonly confidence?: number;\n\treadonly relationRevision: string;\n}\n\nexport interface MemoryTimeRangeV1 {\n\treadonly from?: string;\n\treadonly to?: string;\n}\n\n/** Applicability narrowing; empty means \"applies without narrowing\". */\nexport interface MemoryApplicabilityV1 {\n\treadonly scenes?: readonly string[];\n\treadonly projects?: readonly string[];\n\treadonly tasks?: readonly string[];\n\treadonly timeRange?: MemoryTimeRangeV1;\n\treadonly exceptions?: readonly string[];\n}\n\n/** Preference envelope: the generic schema frozen for 1C; policy semantics come later. */\nexport interface MemoryPreferenceEnvelopeV1 {\n\treadonly subject: \"user\" | \"project\" | \"task\" | \"environment\";\n\treadonly key: string;\n\treadonly preferredValue: JsonValue;\n\treadonly alternatives?: readonly JsonValue[];\n\treadonly scope: {\n\t\treadonly level: \"task\" | \"project\" | \"scene\" | \"timeRange\" | \"profile-private\" | \"user-default\";\n\t\treadonly scenes?: readonly string[];\n\t\treadonly projects?: readonly string[];\n\t\treadonly tasks?: readonly string[];\n\t\treadonly timeRange?: MemoryTimeRangeV1;\n\t\treadonly exceptions?: readonly string[];\n\t};\n\treadonly evidence: {\n\t\treadonly class: \"explicit\" | \"repeated_behavior\" | \"inferred\";\n\t\treadonly sourceRefs: readonly MemorySourceRefV1[];\n\t\treadonly confidence: number;\n\t};\n\treadonly applicabilityConfidence: number;\n\treadonly confirmedAt?: string;\n}\n\n/** Provenance for synthesis/compaction memories derived from other atoms. */\nexport interface MemoryProvenanceV1 {\n\treadonly sourceMemoryIds: readonly string[];\n\treadonly purgeGroupId: string;\n\treadonly containsSourceContent: boolean;\n}\n\nexport type MemoryScopeV1 = \"session\" | \"cycle\" | \"long-term\";\nexport type MemoryKindV1 =\n\t| \"observation\"\n\t| \"preference\"\n\t| \"fact\"\n\t| \"decision\"\n\t| \"constraint\"\n\t| \"inference\"\n\t| \"synthesis\";\nexport type MemoryEvidenceClassV1 = \"explicit\" | \"repeated_behavior\" | \"tool_or_test\" | \"derived\";\n\n/** The immutable canonical atom. Once committed, no field may change in place. */\nexport interface MemoryAtomV1<TPayload = JsonValue> {\n\treadonly memoryId: string;\n\treadonly contractVersion: string;\n\treadonly schemaVersion: number;\n\treadonly scope: MemoryScopeV1;\n\treadonly retentionMode: string;\n\treadonly owner: string;\n\treadonly profileId: string;\n\treadonly shareGroupId?: string;\n\t/**\n\t * Suite (方案) this atom belongs to (方案系统设计 §6.1/§11, M5). Absent\n\t * (or protocol-level `null` on the wire, normalized to absent here) marks a\n\t * legacy pre-M5 atom: such atoms are fail-safe INVISIBLE to every\n\t * suite-scoped read and only readable through suite-unscoped queries. The\n\t * one cross-suite exception is an explicitly promoted user-default\n\t * preference (see {@link memorySuiteVisibility}).\n\t */\n\treadonly suiteId?: string;\n\treadonly retentionPolicyVersion: string;\n\treadonly memoryKind: MemoryKindV1;\n\treadonly payload: TPayload;\n\treadonly preference?: MemoryPreferenceEnvelopeV1;\n\treadonly occurredAt: string;\n\treadonly recordedAt: string;\n\treadonly sourceRefs: readonly MemorySourceRefV1[];\n\treadonly observationId: string;\n\treadonly sessionRefs: readonly string[];\n\treadonly agentInstanceRefs: readonly string[];\n\treadonly projectRefs: readonly string[];\n\treadonly subjectRefs: readonly string[];\n\treadonly facets: readonly MemoryFacetV1[];\n\treadonly relations: readonly MemoryRelationV1[];\n\treadonly provenance?: MemoryProvenanceV1;\n\treadonly confidence: number;\n\treadonly importance: number;\n\treadonly applicability?: MemoryApplicabilityV1;\n\treadonly evidenceClass: MemoryEvidenceClassV1;\n\treadonly contentRevision: string;\n\treadonly writeReason: string;\n}\n\ntype PlainRecord = Record<string, unknown>;\n\nfunction isPlainRecord(value: unknown): value is PlainRecord {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nfunction assertPlainRecord(value: unknown, label: string): asserts value is PlainRecord {\n\tif (!isPlainRecord(value)) throw new Error(`${label} must be a plain object`);\n}\n\nfunction assertExactKeys(\n\tvalue: PlainRecord,\n\trequired: readonly string[],\n\toptional: readonly string[],\n\tlabel: string,\n): void {\n\tconst allowed = new Set([...required, ...optional]);\n\tconst unsupported = Object.keys(value)\n\t\t.filter((key) => !allowed.has(key))\n\t\t.sort();\n\tif (unsupported.length > 0) throw new Error(`${label} has unsupported fields: ${unsupported.join(\", \")}`);\n\tfor (const key of required) {\n\t\tif (!(key in value)) throw new Error(`${label} is missing required field: ${key}`);\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\nfunction assertPositiveSafeInteger(value: unknown, label: string): asserts value is number {\n\tif (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < 1) {\n\t\tthrow new Error(`${label} must be a positive safe integer`);\n\t}\n}\n\nfunction assertUnitInterval(value: unknown, label: string): asserts value is number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value) || value < 0 || value > 1) {\n\t\tthrow new Error(`${label} must be a number between 0 and 1`);\n\t}\n}\n\nfunction assertTimestamp(value: unknown, label: string): asserts value is string {\n\tassertNonEmptyString(value, label);\n\tif (Number.isNaN(Date.parse(value))) throw new Error(`${label} must be an ISO-8601 timestamp`);\n}\n\nfunction assertJsonValue(value: unknown, label: string, seen = new WeakSet<object>()): asserts value is JsonValue {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return;\n\tif (typeof value === \"number\") {\n\t\tif (Number.isFinite(value)) return;\n\t\tthrow new Error(`${label} must contain finite JSON numbers`);\n\t}\n\tif (typeof value !== \"object\") throw new Error(`${label} must be JSON-compatible`);\n\tif (seen.has(value)) throw new Error(`${label} must not contain cycles`);\n\tseen.add(value);\n\tif (Array.isArray(value)) {\n\t\tfor (const [index, child] of value.entries()) assertJsonValue(child, `${label}[${index}]`, seen);\n\t} else {\n\t\tif (!isPlainRecord(value)) throw new Error(`${label} must contain only plain JSON objects`);\n\t\tfor (const [key, child] of Object.entries(value)) assertJsonValue(child, `${label}.${key}`, seen);\n\t}\n\tseen.delete(value);\n}\n\nfunction assertStringArray(value: unknown, label: string): asserts value is readonly string[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tfor (const [index, item] of value.entries()) assertNonEmptyString(item, `${label}[${index}]`);\n}\n\nfunction detachedFreeze<T>(value: T, seen = new WeakMap<object, unknown>()): T {\n\tif (value === null || typeof value !== \"object\") return value;\n\tconst existing = seen.get(value);\n\t// v8 ignore next -- 校验后的原子全部由新副本构成,不存在共享引用\n\tif (existing !== undefined) return existing as T;\n\tif (Array.isArray(value)) {\n\t\tconst copy: unknown[] = [];\n\t\tseen.set(value, copy);\n\t\tfor (const child of value) copy.push(detachedFreeze(child, seen));\n\t\treturn Object.freeze(copy) as T;\n\t}\n\tconst copy: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n\tseen.set(value, copy);\n\tfor (const [key, child] of Object.entries(value as Record<string, unknown>)) {\n\t\tcopy[key] = detachedFreeze(child, seen);\n\t}\n\treturn Object.freeze(copy) as T;\n}\n\n/** Stable JSON serialization with recursively sorted object keys (for identity digests). */\nfunction stableStringify(value: JsonValue): string {\n\tif (value === null || typeof value === \"boolean\" || typeof value === \"number\" || typeof value === \"string\") {\n\t\treturn JSON.stringify(value);\n\t}\n\tif (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(\",\")}]`;\n\tconst entries = Object.keys(value)\n\t\t.sort()\n\t\t.map((key) => `${JSON.stringify(key)}:${stableStringify((value as Record<string, JsonValue>)[key])}`);\n\treturn `{${entries.join(\",\")}}`;\n}\n\nconst MEMORY_SCOPES_V1: readonly MemoryScopeV1[] = [\"session\", \"cycle\", \"long-term\"];\nconst MEMORY_KINDS_V1: readonly MemoryKindV1[] = [\n\t\"observation\",\n\t\"preference\",\n\t\"fact\",\n\t\"decision\",\n\t\"constraint\",\n\t\"inference\",\n\t\"synthesis\",\n];\nconst MEMORY_EVIDENCE_CLASSES_V1: readonly MemoryEvidenceClassV1[] = [\n\t\"explicit\",\n\t\"repeated_behavior\",\n\t\"tool_or_test\",\n\t\"derived\",\n];\nconst SOURCE_REF_KINDS: readonly MemorySourceRefV1[\"kind\"][] = [\"session\", \"entry\", \"artifact\", \"tool\", \"state\"];\n\nfunction validateSourceRef(value: unknown, label: string): MemorySourceRefV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"kind\", \"id\"], [\"revision\", \"digest\"], label);\n\tif (!SOURCE_REF_KINDS.includes(value.kind as MemorySourceRefV1[\"kind\"])) {\n\t\tthrow new Error(`${label}.kind is invalid`);\n\t}\n\tassertNonEmptyString(value.id, `${label}.id`);\n\tif (value.revision !== undefined) assertNonEmptyString(value.revision, `${label}.revision`);\n\tif (value.digest !== undefined) assertNonEmptyString(value.digest, `${label}.digest`);\n\treturn detachedFreeze({\n\t\tkind: value.kind as MemorySourceRefV1[\"kind\"],\n\t\tid: value.id,\n\t\t...(value.revision === undefined ? {} : { revision: value.revision }),\n\t\t...(value.digest === undefined ? {} : { digest: value.digest }),\n\t});\n}\n\nfunction validateFacet(value: unknown, label: string): MemoryFacetV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"namespace\", \"schemaVersion\", \"key\", \"value\"], [], label);\n\tassertNonEmptyString(value.namespace, `${label}.namespace`);\n\tassertPositiveSafeInteger(value.schemaVersion, `${label}.schemaVersion`);\n\tassertNonEmptyString(value.key, `${label}.key`);\n\tassertNonEmptyString(value.value, `${label}.value`);\n\treturn detachedFreeze({\n\t\tnamespace: value.namespace,\n\t\tschemaVersion: value.schemaVersion,\n\t\tkey: value.key,\n\t\tvalue: value.value,\n\t});\n}\n\nfunction validateRelation(value: unknown, label: string): MemoryRelationV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"relationId\", \"namespace\", \"schemaVersion\", \"kind\", \"relationRevision\"],\n\t\t[\"targetMemoryId\", \"targetRef\", \"confidence\"],\n\t\tlabel,\n\t);\n\tassertNonEmptyString(value.relationId, `${label}.relationId`);\n\tassertNonEmptyString(value.namespace, `${label}.namespace`);\n\tassertPositiveSafeInteger(value.schemaVersion, `${label}.schemaVersion`);\n\tassertNonEmptyString(value.kind, `${label}.kind`);\n\tassertNonEmptyString(value.relationRevision, `${label}.relationRevision`);\n\tif (value.targetMemoryId !== undefined && value.targetRef !== undefined) {\n\t\tthrow new Error(`${label} must not declare both targetMemoryId and targetRef`);\n\t}\n\tconst targetMemoryId = value.targetMemoryId === undefined ? undefined : (value.targetMemoryId as string);\n\tif (targetMemoryId !== undefined) assertNonEmptyString(targetMemoryId, `${label}.targetMemoryId`);\n\tconst targetRef =\n\t\tvalue.targetRef === undefined ? undefined : validateSourceRef(value.targetRef, `${label}.targetRef`);\n\tif (value.confidence !== undefined) assertUnitInterval(value.confidence, `${label}.confidence`);\n\treturn detachedFreeze({\n\t\trelationId: value.relationId,\n\t\tnamespace: value.namespace,\n\t\tschemaVersion: value.schemaVersion,\n\t\tkind: value.kind,\n\t\t...(targetMemoryId === undefined ? {} : { targetMemoryId }),\n\t\t...(targetRef === undefined ? {} : { targetRef }),\n\t\t...(value.confidence === undefined ? {} : { confidence: value.confidence }),\n\t\trelationRevision: value.relationRevision,\n\t});\n}\n\nfunction validateTimeRange(value: unknown, label: string): MemoryTimeRangeV1 {\n\tassertPlainRecord(value, label);\n\t// Key order reversed on purpose: in the natural order the boundary-gate\n\t// lexical scanner reads this key pair as an import specifier (first key\n\t// string, comma, second key string). `assertExactKeys` builds a Set, so the\n\t// order is irrelevant to validation.\n\tassertExactKeys(value, [], [\"to\", \"from\"], label);\n\tconst from = value.from === undefined ? undefined : (value.from as string);\n\tconst to = value.to === undefined ? undefined : (value.to as string);\n\tif (from !== undefined) assertTimestamp(from, `${label}.from`);\n\tif (to !== undefined) assertTimestamp(to, `${label}.to`);\n\treturn detachedFreeze({\n\t\t...(from === undefined ? {} : { from }),\n\t\t...(to === undefined ? {} : { to }),\n\t});\n}\n\nfunction validateApplicability(value: unknown, label: string): MemoryApplicabilityV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [], [\"scenes\", \"projects\", \"tasks\", \"timeRange\", \"exceptions\"], label);\n\tconst timeRange =\n\t\tvalue.timeRange === undefined ? undefined : validateTimeRange(value.timeRange, `${label}.timeRange`);\n\treturn detachedFreeze({\n\t\t...(value.scenes === undefined ? {} : { scenes: Object.freeze([...(value.scenes as readonly string[])]) }),\n\t\t...(value.projects === undefined ? {} : { projects: Object.freeze([...(value.projects as readonly string[])]) }),\n\t\t...(value.tasks === undefined ? {} : { tasks: Object.freeze([...(value.tasks as readonly string[])]) }),\n\t\t...(timeRange === undefined ? {} : { timeRange }),\n\t\t...(value.exceptions === undefined\n\t\t\t? {}\n\t\t\t: { exceptions: Object.freeze([...(value.exceptions as readonly string[])]) }),\n\t});\n}\n\nfunction validatePreferenceEnvelope(value: unknown, label: string): MemoryPreferenceEnvelopeV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"subject\", \"key\", \"preferredValue\", \"scope\", \"evidence\", \"applicabilityConfidence\"],\n\t\t[\"alternatives\", \"confirmedAt\"],\n\t\tlabel,\n\t);\n\tconst subjects: readonly MemoryPreferenceEnvelopeV1[\"subject\"][] = [\"user\", \"project\", \"task\", \"environment\"];\n\tif (!subjects.includes(value.subject as MemoryPreferenceEnvelopeV1[\"subject\"])) {\n\t\tthrow new Error(`${label}.subject is invalid`);\n\t}\n\tassertNonEmptyString(value.key, `${label}.key`);\n\tassertJsonValue(value.preferredValue, `${label}.preferredValue`);\n\tif (value.alternatives !== undefined) {\n\t\tif (!Array.isArray(value.alternatives)) throw new Error(`${label}.alternatives must be an array`);\n\t\tfor (const [index, item] of value.alternatives.entries()) {\n\t\t\tassertJsonValue(item, `${label}.alternatives[${index}]`);\n\t\t}\n\t}\n\tconst scope = value.scope;\n\tassertPlainRecord(scope, `${label}.scope`);\n\tassertExactKeys(scope, [\"level\"], [\"scenes\", \"projects\", \"tasks\", \"timeRange\", \"exceptions\"], `${label}.scope`);\n\tconst levels: readonly MemoryPreferenceEnvelopeV1[\"scope\"][\"level\"][] = [\n\t\t\"task\",\n\t\t\"project\",\n\t\t\"scene\",\n\t\t\"timeRange\",\n\t\t\"profile-private\",\n\t\t\"user-default\",\n\t];\n\tif (!levels.includes(scope.level as MemoryPreferenceEnvelopeV1[\"scope\"][\"level\"])) {\n\t\tthrow new Error(`${label}.scope.level is invalid`);\n\t}\n\t// Anchor levels require their anchor to be present (记录模型约束).\n\tconst anchors: ReadonlyArray<[string, keyof PlainRecord]> = [\n\t\t[\"task\", \"tasks\"],\n\t\t[\"project\", \"projects\"],\n\t\t[\"scene\", \"scenes\"],\n\t\t[\"timeRange\", \"timeRange\"],\n\t];\n\tfor (const [level, field] of anchors) {\n\t\tif (\n\t\t\tscope.level === level &&\n\t\t\t(scope[field] === undefined || (Array.isArray(scope[field]) && scope[field].length === 0))\n\t\t) {\n\t\t\tthrow new Error(`${label}.scope.level \"${level}\" requires non-empty ${field}`);\n\t\t}\n\t}\n\tconst evidence = value.evidence;\n\tassertPlainRecord(evidence, `${label}.evidence`);\n\tassertExactKeys(evidence, [\"class\", \"sourceRefs\", \"confidence\"], [], `${label}.evidence`);\n\tconst evidenceClasses: readonly MemoryPreferenceEnvelopeV1[\"evidence\"][\"class\"][] = [\n\t\t\"explicit\",\n\t\t\"repeated_behavior\",\n\t\t\"inferred\",\n\t];\n\tif (!evidenceClasses.includes(evidence.class as MemoryPreferenceEnvelopeV1[\"evidence\"][\"class\"])) {\n\t\tthrow new Error(`${label}.evidence.class is invalid`);\n\t}\n\tif (!Array.isArray(evidence.sourceRefs) || evidence.sourceRefs.length === 0) {\n\t\tthrow new Error(`${label}.evidence.sourceRefs must not be empty`);\n\t}\n\tconst evidenceSourceRefs = evidence.sourceRefs.map((item, index) =>\n\t\tvalidateSourceRef(item, `${label}.evidence.sourceRefs[${index}]`),\n\t);\n\tassertUnitInterval(evidence.confidence, `${label}.evidence.confidence`);\n\tassertUnitInterval(value.applicabilityConfidence, `${label}.applicabilityConfidence`);\n\tif (value.confirmedAt !== undefined) assertTimestamp(value.confirmedAt, `${label}.confirmedAt`);\n\treturn detachedFreeze({\n\t\tsubject: value.subject as MemoryPreferenceEnvelopeV1[\"subject\"],\n\t\tkey: value.key,\n\t\tpreferredValue: value.preferredValue,\n\t\t...(value.alternatives === undefined ? {} : { alternatives: Object.freeze([...value.alternatives]) }),\n\t\tscope: detachedFreeze({\n\t\t\tlevel: scope.level as MemoryPreferenceEnvelopeV1[\"scope\"][\"level\"],\n\t\t\t...(scope.scenes === undefined ? {} : { scenes: Object.freeze([...(scope.scenes as readonly string[])]) }),\n\t\t\t...(scope.projects === undefined\n\t\t\t\t? {}\n\t\t\t\t: { projects: Object.freeze([...(scope.projects as readonly string[])]) }),\n\t\t\t...(scope.tasks === undefined ? {} : { tasks: Object.freeze([...(scope.tasks as readonly string[])]) }),\n\t\t\t...(scope.timeRange === undefined\n\t\t\t\t? {}\n\t\t\t\t: { timeRange: validateTimeRange(scope.timeRange, `${label}.scope.timeRange`) }),\n\t\t\t...(scope.exceptions === undefined\n\t\t\t\t? {}\n\t\t\t\t: { exceptions: Object.freeze([...(scope.exceptions as readonly string[])]) }),\n\t\t}),\n\t\tevidence: detachedFreeze({\n\t\t\tclass: evidence.class as MemoryPreferenceEnvelopeV1[\"evidence\"][\"class\"],\n\t\t\tsourceRefs: Object.freeze(evidenceSourceRefs),\n\t\t\tconfidence: evidence.confidence,\n\t\t}),\n\t\tapplicabilityConfidence: value.applicabilityConfidence,\n\t\t...(value.confirmedAt === undefined ? {} : { confirmedAt: value.confirmedAt }),\n\t}) as MemoryPreferenceEnvelopeV1;\n}\n\nfunction validateProvenance(value: unknown, label: string): MemoryProvenanceV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"sourceMemoryIds\", \"purgeGroupId\", \"containsSourceContent\"], [], label);\n\tassertStringArray(value.sourceMemoryIds, `${label}.sourceMemoryIds`);\n\tif (value.sourceMemoryIds.length === 0) throw new Error(`${label}.sourceMemoryIds must not be empty`);\n\tassertNonEmptyString(value.purgeGroupId, `${label}.purgeGroupId`);\n\tif (typeof value.containsSourceContent !== \"boolean\") {\n\t\tthrow new Error(`${label}.containsSourceContent must be boolean`);\n\t}\n\treturn detachedFreeze({\n\t\tsourceMemoryIds: Object.freeze([...(value.sourceMemoryIds as readonly string[])]),\n\t\tpurgeGroupId: value.purgeGroupId,\n\t\tcontainsSourceContent: value.containsSourceContent,\n\t});\n}\n\n/**\n * Validates a canonical memory atom and returns a detached, deep-frozen\n * snapshot. Unknown fields, wrong enums, non-JSON payloads, preference-kind\n * atoms without a preference envelope, and identity fields (memoryId,\n * observationId, contentRevision, owner) that are not non-empty strings are\n * rejected.\n */\nexport function validateMemoryAtomV1<TPayload = JsonValue>(value: unknown): MemoryAtomV1<TPayload> {\n\tassertPlainRecord(value, \"Memory atom\");\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\n\t\t\t\"memoryId\",\n\t\t\t\"contractVersion\",\n\t\t\t\"schemaVersion\",\n\t\t\t\"scope\",\n\t\t\t\"retentionMode\",\n\t\t\t\"owner\",\n\t\t\t\"profileId\",\n\t\t\t\"retentionPolicyVersion\",\n\t\t\t\"memoryKind\",\n\t\t\t\"payload\",\n\t\t\t\"occurredAt\",\n\t\t\t\"recordedAt\",\n\t\t\t\"sourceRefs\",\n\t\t\t\"observationId\",\n\t\t\t\"sessionRefs\",\n\t\t\t\"agentInstanceRefs\",\n\t\t\t\"projectRefs\",\n\t\t\t\"subjectRefs\",\n\t\t\t\"facets\",\n\t\t\t\"relations\",\n\t\t\t\"confidence\",\n\t\t\t\"importance\",\n\t\t\t\"evidenceClass\",\n\t\t\t\"contentRevision\",\n\t\t\t\"writeReason\",\n\t\t],\n\t\t[\"tenantId\", \"shareGroupId\", \"suiteId\", \"preference\", \"provenance\", \"applicability\"],\n\t\t\"Memory atom\",\n\t);\n\tassertNonEmptyString(value.memoryId, \"Memory atom.memoryId\");\n\tassertNonEmptyString(value.contractVersion, \"Memory atom.contractVersion\");\n\tassertPositiveSafeInteger(value.schemaVersion, \"Memory atom.schemaVersion\");\n\tif (!MEMORY_SCOPES_V1.includes(value.scope as MemoryScopeV1)) throw new Error(\"Memory atom.scope is invalid\");\n\tassertNonEmptyString(value.retentionMode, \"Memory atom.retentionMode\");\n\tassertNonEmptyString(value.owner, \"Memory atom.owner\");\n\tassertNonEmptyString(value.profileId, \"Memory atom.profileId\");\n\tif (value.shareGroupId !== undefined) assertNonEmptyString(value.shareGroupId, \"Memory atom.shareGroupId\");\n\t// Legacy (pre-M5) atoms may carry protocol-level `null` for \"no suite\"; it\n\t// is tolerated here and normalized to an absent field (存量语义), while a\n\t// present suiteId must be a non-empty string.\n\tif (value.suiteId !== undefined && value.suiteId !== null) {\n\t\tassertNonEmptyString(value.suiteId, \"Memory atom.suiteId\");\n\t}\n\tassertNonEmptyString(value.retentionPolicyVersion, \"Memory atom.retentionPolicyVersion\");\n\tif (!MEMORY_KINDS_V1.includes(value.memoryKind as MemoryKindV1))\n\t\tthrow new Error(\"Memory atom.memoryKind is invalid\");\n\tassertJsonValue(value.payload, \"Memory atom.payload\");\n\tif (value.memoryKind === \"preference\" && value.preference === undefined) {\n\t\tthrow new Error(\"Memory atom.memoryKind preference requires a preference envelope\");\n\t}\n\tconst preference =\n\t\tvalue.preference === undefined\n\t\t\t? undefined\n\t\t\t: validatePreferenceEnvelope(value.preference, \"Memory atom.preference\");\n\tassertTimestamp(value.occurredAt, \"Memory atom.occurredAt\");\n\tassertTimestamp(value.recordedAt, \"Memory atom.recordedAt\");\n\tif (!Array.isArray(value.sourceRefs)) throw new Error(\"Memory atom.sourceRefs must be an array\");\n\tconst sourceRefs = value.sourceRefs.map((item, index) =>\n\t\tvalidateSourceRef(item, `Memory atom.sourceRefs[${index}]`),\n\t);\n\tassertNonEmptyString(value.observationId, \"Memory atom.observationId\");\n\tassertStringArray(value.sessionRefs, \"Memory atom.sessionRefs\");\n\tassertStringArray(value.agentInstanceRefs, \"Memory atom.agentInstanceRefs\");\n\tassertStringArray(value.projectRefs, \"Memory atom.projectRefs\");\n\tassertStringArray(value.subjectRefs, \"Memory atom.subjectRefs\");\n\tif (!Array.isArray(value.facets)) throw new Error(\"Memory atom.facets must be an array\");\n\tconst facets = value.facets.map((item, index) => validateFacet(item, `Memory atom.facets[${index}]`));\n\tif (!Array.isArray(value.relations)) throw new Error(\"Memory atom.relations must be an array\");\n\tconst relations = value.relations.map((item, index) => validateRelation(item, `Memory atom.relations[${index}]`));\n\tconst provenance =\n\t\tvalue.provenance === undefined ? undefined : validateProvenance(value.provenance, \"Memory atom.provenance\");\n\tassertUnitInterval(value.confidence, \"Memory atom.confidence\");\n\tassertUnitInterval(value.importance, \"Memory atom.importance\");\n\tconst applicability =\n\t\tvalue.applicability === undefined\n\t\t\t? undefined\n\t\t\t: validateApplicability(value.applicability, \"Memory atom.applicability\");\n\tif (!MEMORY_EVIDENCE_CLASSES_V1.includes(value.evidenceClass as MemoryEvidenceClassV1)) {\n\t\tthrow new Error(\"Memory atom.evidenceClass is invalid\");\n\t}\n\tassertNonEmptyString(value.contentRevision, \"Memory atom.contentRevision\");\n\tassertNonEmptyString(value.writeReason, \"Memory atom.writeReason\");\n\n\treturn detachedFreeze({\n\t\tmemoryId: value.memoryId,\n\t\tcontractVersion: value.contractVersion,\n\t\tschemaVersion: value.schemaVersion,\n\t\tscope: value.scope as MemoryScopeV1,\n\t\tretentionMode: value.retentionMode,\n\t\towner: value.owner,\n\t\tprofileId: value.profileId,\n\t\t...(value.shareGroupId === undefined ? {} : { shareGroupId: value.shareGroupId }),\n\t\t...(value.suiteId === undefined || value.suiteId === null ? {} : { suiteId: value.suiteId }),\n\t\tretentionPolicyVersion: value.retentionPolicyVersion,\n\t\tmemoryKind: value.memoryKind as MemoryKindV1,\n\t\tpayload: value.payload as TPayload,\n\t\t...(preference === undefined ? {} : { preference }),\n\t\toccurredAt: value.occurredAt,\n\t\trecordedAt: value.recordedAt,\n\t\tsourceRefs: Object.freeze(sourceRefs),\n\t\tobservationId: value.observationId,\n\t\tsessionRefs: Object.freeze([...value.sessionRefs]),\n\t\tagentInstanceRefs: Object.freeze([...value.agentInstanceRefs]),\n\t\tprojectRefs: Object.freeze([...value.projectRefs]),\n\t\tsubjectRefs: Object.freeze([...value.subjectRefs]),\n\t\tfacets: Object.freeze(facets),\n\t\trelations: Object.freeze(relations),\n\t\t...(provenance === undefined ? {} : { provenance }),\n\t\tconfidence: value.confidence,\n\t\timportance: value.importance,\n\t\t...(applicability === undefined ? {} : { applicability }),\n\t\tevidenceClass: value.evidenceClass as MemoryEvidenceClassV1,\n\t\tcontentRevision: value.contentRevision,\n\t\twriteReason: value.writeReason,\n\t}) as MemoryAtomV1<TPayload>;\n}\n\n/** Builds a detached, frozen canonical atom (validate + freeze). */\nexport function buildMemoryAtomV1<TPayload = JsonValue>(atom: MemoryAtomV1<TPayload>): MemoryAtomV1<TPayload> {\n\treturn validateMemoryAtomV1<TPayload>(atom);\n}\n\n/** FNV-1a 64-bit digest over the stable JSON serialization of `value`. */\nexport function memoryContentDigest(value: JsonValue): string {\n\tconst serialized = stableStringify(value);\n\tlet digest = 14695981039346656037n;\n\tfor (const byte of new TextEncoder().encode(serialized)) {\n\t\tdigest ^= BigInt(byte);\n\t\tdigest = BigInt.asUintN(64, digest * 1099511628211n);\n\t}\n\treturn digest.toString(16).padStart(16, \"0\");\n}\n\n/** Idempotent submission identity: the same (owner, observationId) is one observation. */\nexport function memoryObservationKey(owner: string, observationId: string): string {\n\tassertNonEmptyString(owner, \"owner\");\n\tassertNonEmptyString(observationId, \"observationId\");\n\treturn `${owner}\\u0000${observationId}`;\n}\n\n// ---------------------------------------------------------------------------\n// Suite (方案) read boundary (方案系统设计 §6.1/§11, M5)\n// ---------------------------------------------------------------------------\n\n/** Why a record was excluded from a suite-scoped read (读取边界惰性可观测). */\nexport type MemorySuiteVisibilityV1 =\n\t/** The atom matches the queried suite, or is an explicitly promoted user-default preference. */\n\t| \"visible\"\n\t/** Legacy (pre-M5) atom without a suiteId — fail-safe invisible to every suite. */\n\t| \"legacy\"\n\t/** The atom belongs to a different suite. */\n\t| \"foreign-suite\";\n\n/**\n * Skipped-record counters attached to suite-scoped reads. The read boundary is\n * lazy and observable (设计 §11 三段式): excluded atoms are counted, never\n * silently dropped.\n */\nexport interface MemorySuiteFilterStatsV1 {\n\t/** Legacy (suiteId-less) atoms skipped as invisible to any suite. */\n\treadonly legacySkipped: number;\n\t/** Atoms bound to a different suiteId that were skipped. */\n\treadonly foreignSuiteSkipped: number;\n}\n\n/**\n * Suite-scoped visibility of one atom (方案系统设计 §6.1/§11):\n * - `atom.suiteId === suiteId` → visible;\n * - an explicitly promoted user-default preference (scope.level\n * \"user-default\" WITH `confirmedAt` — the canonical explicit-confirmation\n * marker set only by the explicit promotion channel) ignores the suiteId\n * filter and is visible in every suite (§6.1 跨方案偏好);\n * - absent suiteId (legacy/存量) → invisible to every suite (fail-safe\n * default), counted via {@link MemorySuiteFilterStatsV1};\n * - any other suiteId → invisible.\n */\nexport function memorySuiteVisibility(atom: MemoryAtomV1, suiteId: string): MemorySuiteVisibilityV1 {\n\tassertNonEmptyString(suiteId, \"suiteId\");\n\tif (\n\t\tatom.memoryKind === \"preference\" &&\n\t\tatom.preference?.scope.level === \"user-default\" &&\n\t\tatom.preference.confirmedAt !== undefined\n\t) {\n\t\treturn \"visible\";\n\t}\n\tif (atom.suiteId === undefined) return \"legacy\";\n\tif (atom.suiteId === suiteId) return \"visible\";\n\treturn \"foreign-suite\";\n}\n"]}
@@ -0,0 +1,487 @@
1
+ function isPlainRecord(value) {
2
+ if (value === null || typeof value !== "object" || Array.isArray(value))
3
+ return false;
4
+ const prototype = Object.getPrototypeOf(value);
5
+ return prototype === Object.prototype || prototype === null;
6
+ }
7
+ function assertPlainRecord(value, label) {
8
+ if (!isPlainRecord(value))
9
+ throw new Error(`${label} must be a plain object`);
10
+ }
11
+ function assertExactKeys(value, required, optional, label) {
12
+ const allowed = new Set([...required, ...optional]);
13
+ const unsupported = Object.keys(value)
14
+ .filter((key) => !allowed.has(key))
15
+ .sort();
16
+ if (unsupported.length > 0)
17
+ throw new Error(`${label} has unsupported fields: ${unsupported.join(", ")}`);
18
+ for (const key of required) {
19
+ if (!(key in value))
20
+ throw new Error(`${label} is missing required field: ${key}`);
21
+ }
22
+ }
23
+ function assertNonEmptyString(value, label) {
24
+ if (typeof value !== "string" || value.trim().length === 0)
25
+ throw new Error(`${label} must be a non-empty string`);
26
+ }
27
+ function assertPositiveSafeInteger(value, label) {
28
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
29
+ throw new Error(`${label} must be a positive safe integer`);
30
+ }
31
+ }
32
+ function assertUnitInterval(value, label) {
33
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
34
+ throw new Error(`${label} must be a number between 0 and 1`);
35
+ }
36
+ }
37
+ function assertTimestamp(value, label) {
38
+ assertNonEmptyString(value, label);
39
+ if (Number.isNaN(Date.parse(value)))
40
+ throw new Error(`${label} must be an ISO-8601 timestamp`);
41
+ }
42
+ function assertJsonValue(value, label, seen = new WeakSet()) {
43
+ if (value === null || typeof value === "string" || typeof value === "boolean")
44
+ return;
45
+ if (typeof value === "number") {
46
+ if (Number.isFinite(value))
47
+ return;
48
+ throw new Error(`${label} must contain finite JSON numbers`);
49
+ }
50
+ if (typeof value !== "object")
51
+ throw new Error(`${label} must be JSON-compatible`);
52
+ if (seen.has(value))
53
+ throw new Error(`${label} must not contain cycles`);
54
+ seen.add(value);
55
+ if (Array.isArray(value)) {
56
+ for (const [index, child] of value.entries())
57
+ assertJsonValue(child, `${label}[${index}]`, seen);
58
+ }
59
+ else {
60
+ if (!isPlainRecord(value))
61
+ throw new Error(`${label} must contain only plain JSON objects`);
62
+ for (const [key, child] of Object.entries(value))
63
+ assertJsonValue(child, `${label}.${key}`, seen);
64
+ }
65
+ seen.delete(value);
66
+ }
67
+ function assertStringArray(value, label) {
68
+ if (!Array.isArray(value))
69
+ throw new Error(`${label} must be an array`);
70
+ for (const [index, item] of value.entries())
71
+ assertNonEmptyString(item, `${label}[${index}]`);
72
+ }
73
+ function detachedFreeze(value, seen = new WeakMap()) {
74
+ if (value === null || typeof value !== "object")
75
+ return value;
76
+ const existing = seen.get(value);
77
+ // v8 ignore next -- 校验后的原子全部由新副本构成,不存在共享引用
78
+ if (existing !== undefined)
79
+ return existing;
80
+ if (Array.isArray(value)) {
81
+ const copy = [];
82
+ seen.set(value, copy);
83
+ for (const child of value)
84
+ copy.push(detachedFreeze(child, seen));
85
+ return Object.freeze(copy);
86
+ }
87
+ const copy = Object.create(null);
88
+ seen.set(value, copy);
89
+ for (const [key, child] of Object.entries(value)) {
90
+ copy[key] = detachedFreeze(child, seen);
91
+ }
92
+ return Object.freeze(copy);
93
+ }
94
+ /** Stable JSON serialization with recursively sorted object keys (for identity digests). */
95
+ function stableStringify(value) {
96
+ if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
97
+ return JSON.stringify(value);
98
+ }
99
+ if (Array.isArray(value))
100
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
101
+ const entries = Object.keys(value)
102
+ .sort()
103
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
104
+ return `{${entries.join(",")}}`;
105
+ }
106
+ const MEMORY_SCOPES_V1 = ["session", "cycle", "long-term"];
107
+ const MEMORY_KINDS_V1 = [
108
+ "observation",
109
+ "preference",
110
+ "fact",
111
+ "decision",
112
+ "constraint",
113
+ "inference",
114
+ "synthesis",
115
+ ];
116
+ const MEMORY_EVIDENCE_CLASSES_V1 = [
117
+ "explicit",
118
+ "repeated_behavior",
119
+ "tool_or_test",
120
+ "derived",
121
+ ];
122
+ const SOURCE_REF_KINDS = ["session", "entry", "artifact", "tool", "state"];
123
+ function validateSourceRef(value, label) {
124
+ assertPlainRecord(value, label);
125
+ assertExactKeys(value, ["kind", "id"], ["revision", "digest"], label);
126
+ if (!SOURCE_REF_KINDS.includes(value.kind)) {
127
+ throw new Error(`${label}.kind is invalid`);
128
+ }
129
+ assertNonEmptyString(value.id, `${label}.id`);
130
+ if (value.revision !== undefined)
131
+ assertNonEmptyString(value.revision, `${label}.revision`);
132
+ if (value.digest !== undefined)
133
+ assertNonEmptyString(value.digest, `${label}.digest`);
134
+ return detachedFreeze({
135
+ kind: value.kind,
136
+ id: value.id,
137
+ ...(value.revision === undefined ? {} : { revision: value.revision }),
138
+ ...(value.digest === undefined ? {} : { digest: value.digest }),
139
+ });
140
+ }
141
+ function validateFacet(value, label) {
142
+ assertPlainRecord(value, label);
143
+ assertExactKeys(value, ["namespace", "schemaVersion", "key", "value"], [], label);
144
+ assertNonEmptyString(value.namespace, `${label}.namespace`);
145
+ assertPositiveSafeInteger(value.schemaVersion, `${label}.schemaVersion`);
146
+ assertNonEmptyString(value.key, `${label}.key`);
147
+ assertNonEmptyString(value.value, `${label}.value`);
148
+ return detachedFreeze({
149
+ namespace: value.namespace,
150
+ schemaVersion: value.schemaVersion,
151
+ key: value.key,
152
+ value: value.value,
153
+ });
154
+ }
155
+ function validateRelation(value, label) {
156
+ assertPlainRecord(value, label);
157
+ assertExactKeys(value, ["relationId", "namespace", "schemaVersion", "kind", "relationRevision"], ["targetMemoryId", "targetRef", "confidence"], label);
158
+ assertNonEmptyString(value.relationId, `${label}.relationId`);
159
+ assertNonEmptyString(value.namespace, `${label}.namespace`);
160
+ assertPositiveSafeInteger(value.schemaVersion, `${label}.schemaVersion`);
161
+ assertNonEmptyString(value.kind, `${label}.kind`);
162
+ assertNonEmptyString(value.relationRevision, `${label}.relationRevision`);
163
+ if (value.targetMemoryId !== undefined && value.targetRef !== undefined) {
164
+ throw new Error(`${label} must not declare both targetMemoryId and targetRef`);
165
+ }
166
+ const targetMemoryId = value.targetMemoryId === undefined ? undefined : value.targetMemoryId;
167
+ if (targetMemoryId !== undefined)
168
+ assertNonEmptyString(targetMemoryId, `${label}.targetMemoryId`);
169
+ const targetRef = value.targetRef === undefined ? undefined : validateSourceRef(value.targetRef, `${label}.targetRef`);
170
+ if (value.confidence !== undefined)
171
+ assertUnitInterval(value.confidence, `${label}.confidence`);
172
+ return detachedFreeze({
173
+ relationId: value.relationId,
174
+ namespace: value.namespace,
175
+ schemaVersion: value.schemaVersion,
176
+ kind: value.kind,
177
+ ...(targetMemoryId === undefined ? {} : { targetMemoryId }),
178
+ ...(targetRef === undefined ? {} : { targetRef }),
179
+ ...(value.confidence === undefined ? {} : { confidence: value.confidence }),
180
+ relationRevision: value.relationRevision,
181
+ });
182
+ }
183
+ function validateTimeRange(value, label) {
184
+ assertPlainRecord(value, label);
185
+ // Key order reversed on purpose: in the natural order the boundary-gate
186
+ // lexical scanner reads this key pair as an import specifier (first key
187
+ // string, comma, second key string). `assertExactKeys` builds a Set, so the
188
+ // order is irrelevant to validation.
189
+ assertExactKeys(value, [], ["to", "from"], label);
190
+ const from = value.from === undefined ? undefined : value.from;
191
+ const to = value.to === undefined ? undefined : value.to;
192
+ if (from !== undefined)
193
+ assertTimestamp(from, `${label}.from`);
194
+ if (to !== undefined)
195
+ assertTimestamp(to, `${label}.to`);
196
+ return detachedFreeze({
197
+ ...(from === undefined ? {} : { from }),
198
+ ...(to === undefined ? {} : { to }),
199
+ });
200
+ }
201
+ function validateApplicability(value, label) {
202
+ assertPlainRecord(value, label);
203
+ assertExactKeys(value, [], ["scenes", "projects", "tasks", "timeRange", "exceptions"], label);
204
+ const timeRange = value.timeRange === undefined ? undefined : validateTimeRange(value.timeRange, `${label}.timeRange`);
205
+ return detachedFreeze({
206
+ ...(value.scenes === undefined ? {} : { scenes: Object.freeze([...value.scenes]) }),
207
+ ...(value.projects === undefined ? {} : { projects: Object.freeze([...value.projects]) }),
208
+ ...(value.tasks === undefined ? {} : { tasks: Object.freeze([...value.tasks]) }),
209
+ ...(timeRange === undefined ? {} : { timeRange }),
210
+ ...(value.exceptions === undefined
211
+ ? {}
212
+ : { exceptions: Object.freeze([...value.exceptions]) }),
213
+ });
214
+ }
215
+ function validatePreferenceEnvelope(value, label) {
216
+ assertPlainRecord(value, label);
217
+ assertExactKeys(value, ["subject", "key", "preferredValue", "scope", "evidence", "applicabilityConfidence"], ["alternatives", "confirmedAt"], label);
218
+ const subjects = ["user", "project", "task", "environment"];
219
+ if (!subjects.includes(value.subject)) {
220
+ throw new Error(`${label}.subject is invalid`);
221
+ }
222
+ assertNonEmptyString(value.key, `${label}.key`);
223
+ assertJsonValue(value.preferredValue, `${label}.preferredValue`);
224
+ if (value.alternatives !== undefined) {
225
+ if (!Array.isArray(value.alternatives))
226
+ throw new Error(`${label}.alternatives must be an array`);
227
+ for (const [index, item] of value.alternatives.entries()) {
228
+ assertJsonValue(item, `${label}.alternatives[${index}]`);
229
+ }
230
+ }
231
+ const scope = value.scope;
232
+ assertPlainRecord(scope, `${label}.scope`);
233
+ assertExactKeys(scope, ["level"], ["scenes", "projects", "tasks", "timeRange", "exceptions"], `${label}.scope`);
234
+ const levels = [
235
+ "task",
236
+ "project",
237
+ "scene",
238
+ "timeRange",
239
+ "profile-private",
240
+ "user-default",
241
+ ];
242
+ if (!levels.includes(scope.level)) {
243
+ throw new Error(`${label}.scope.level is invalid`);
244
+ }
245
+ // Anchor levels require their anchor to be present (记录模型约束).
246
+ const anchors = [
247
+ ["task", "tasks"],
248
+ ["project", "projects"],
249
+ ["scene", "scenes"],
250
+ ["timeRange", "timeRange"],
251
+ ];
252
+ for (const [level, field] of anchors) {
253
+ if (scope.level === level &&
254
+ (scope[field] === undefined || (Array.isArray(scope[field]) && scope[field].length === 0))) {
255
+ throw new Error(`${label}.scope.level "${level}" requires non-empty ${field}`);
256
+ }
257
+ }
258
+ const evidence = value.evidence;
259
+ assertPlainRecord(evidence, `${label}.evidence`);
260
+ assertExactKeys(evidence, ["class", "sourceRefs", "confidence"], [], `${label}.evidence`);
261
+ const evidenceClasses = [
262
+ "explicit",
263
+ "repeated_behavior",
264
+ "inferred",
265
+ ];
266
+ if (!evidenceClasses.includes(evidence.class)) {
267
+ throw new Error(`${label}.evidence.class is invalid`);
268
+ }
269
+ if (!Array.isArray(evidence.sourceRefs) || evidence.sourceRefs.length === 0) {
270
+ throw new Error(`${label}.evidence.sourceRefs must not be empty`);
271
+ }
272
+ const evidenceSourceRefs = evidence.sourceRefs.map((item, index) => validateSourceRef(item, `${label}.evidence.sourceRefs[${index}]`));
273
+ assertUnitInterval(evidence.confidence, `${label}.evidence.confidence`);
274
+ assertUnitInterval(value.applicabilityConfidence, `${label}.applicabilityConfidence`);
275
+ if (value.confirmedAt !== undefined)
276
+ assertTimestamp(value.confirmedAt, `${label}.confirmedAt`);
277
+ return detachedFreeze({
278
+ subject: value.subject,
279
+ key: value.key,
280
+ preferredValue: value.preferredValue,
281
+ ...(value.alternatives === undefined ? {} : { alternatives: Object.freeze([...value.alternatives]) }),
282
+ scope: detachedFreeze({
283
+ level: scope.level,
284
+ ...(scope.scenes === undefined ? {} : { scenes: Object.freeze([...scope.scenes]) }),
285
+ ...(scope.projects === undefined
286
+ ? {}
287
+ : { projects: Object.freeze([...scope.projects]) }),
288
+ ...(scope.tasks === undefined ? {} : { tasks: Object.freeze([...scope.tasks]) }),
289
+ ...(scope.timeRange === undefined
290
+ ? {}
291
+ : { timeRange: validateTimeRange(scope.timeRange, `${label}.scope.timeRange`) }),
292
+ ...(scope.exceptions === undefined
293
+ ? {}
294
+ : { exceptions: Object.freeze([...scope.exceptions]) }),
295
+ }),
296
+ evidence: detachedFreeze({
297
+ class: evidence.class,
298
+ sourceRefs: Object.freeze(evidenceSourceRefs),
299
+ confidence: evidence.confidence,
300
+ }),
301
+ applicabilityConfidence: value.applicabilityConfidence,
302
+ ...(value.confirmedAt === undefined ? {} : { confirmedAt: value.confirmedAt }),
303
+ });
304
+ }
305
+ function validateProvenance(value, label) {
306
+ assertPlainRecord(value, label);
307
+ assertExactKeys(value, ["sourceMemoryIds", "purgeGroupId", "containsSourceContent"], [], label);
308
+ assertStringArray(value.sourceMemoryIds, `${label}.sourceMemoryIds`);
309
+ if (value.sourceMemoryIds.length === 0)
310
+ throw new Error(`${label}.sourceMemoryIds must not be empty`);
311
+ assertNonEmptyString(value.purgeGroupId, `${label}.purgeGroupId`);
312
+ if (typeof value.containsSourceContent !== "boolean") {
313
+ throw new Error(`${label}.containsSourceContent must be boolean`);
314
+ }
315
+ return detachedFreeze({
316
+ sourceMemoryIds: Object.freeze([...value.sourceMemoryIds]),
317
+ purgeGroupId: value.purgeGroupId,
318
+ containsSourceContent: value.containsSourceContent,
319
+ });
320
+ }
321
+ /**
322
+ * Validates a canonical memory atom and returns a detached, deep-frozen
323
+ * snapshot. Unknown fields, wrong enums, non-JSON payloads, preference-kind
324
+ * atoms without a preference envelope, and identity fields (memoryId,
325
+ * observationId, contentRevision, owner) that are not non-empty strings are
326
+ * rejected.
327
+ */
328
+ export function validateMemoryAtomV1(value) {
329
+ assertPlainRecord(value, "Memory atom");
330
+ assertExactKeys(value, [
331
+ "memoryId",
332
+ "contractVersion",
333
+ "schemaVersion",
334
+ "scope",
335
+ "retentionMode",
336
+ "owner",
337
+ "profileId",
338
+ "retentionPolicyVersion",
339
+ "memoryKind",
340
+ "payload",
341
+ "occurredAt",
342
+ "recordedAt",
343
+ "sourceRefs",
344
+ "observationId",
345
+ "sessionRefs",
346
+ "agentInstanceRefs",
347
+ "projectRefs",
348
+ "subjectRefs",
349
+ "facets",
350
+ "relations",
351
+ "confidence",
352
+ "importance",
353
+ "evidenceClass",
354
+ "contentRevision",
355
+ "writeReason",
356
+ ], ["tenantId", "shareGroupId", "suiteId", "preference", "provenance", "applicability"], "Memory atom");
357
+ assertNonEmptyString(value.memoryId, "Memory atom.memoryId");
358
+ assertNonEmptyString(value.contractVersion, "Memory atom.contractVersion");
359
+ assertPositiveSafeInteger(value.schemaVersion, "Memory atom.schemaVersion");
360
+ if (!MEMORY_SCOPES_V1.includes(value.scope))
361
+ throw new Error("Memory atom.scope is invalid");
362
+ assertNonEmptyString(value.retentionMode, "Memory atom.retentionMode");
363
+ assertNonEmptyString(value.owner, "Memory atom.owner");
364
+ assertNonEmptyString(value.profileId, "Memory atom.profileId");
365
+ if (value.shareGroupId !== undefined)
366
+ assertNonEmptyString(value.shareGroupId, "Memory atom.shareGroupId");
367
+ // Legacy (pre-M5) atoms may carry protocol-level `null` for "no suite"; it
368
+ // is tolerated here and normalized to an absent field (存量语义), while a
369
+ // present suiteId must be a non-empty string.
370
+ if (value.suiteId !== undefined && value.suiteId !== null) {
371
+ assertNonEmptyString(value.suiteId, "Memory atom.suiteId");
372
+ }
373
+ assertNonEmptyString(value.retentionPolicyVersion, "Memory atom.retentionPolicyVersion");
374
+ if (!MEMORY_KINDS_V1.includes(value.memoryKind))
375
+ throw new Error("Memory atom.memoryKind is invalid");
376
+ assertJsonValue(value.payload, "Memory atom.payload");
377
+ if (value.memoryKind === "preference" && value.preference === undefined) {
378
+ throw new Error("Memory atom.memoryKind preference requires a preference envelope");
379
+ }
380
+ const preference = value.preference === undefined
381
+ ? undefined
382
+ : validatePreferenceEnvelope(value.preference, "Memory atom.preference");
383
+ assertTimestamp(value.occurredAt, "Memory atom.occurredAt");
384
+ assertTimestamp(value.recordedAt, "Memory atom.recordedAt");
385
+ if (!Array.isArray(value.sourceRefs))
386
+ throw new Error("Memory atom.sourceRefs must be an array");
387
+ const sourceRefs = value.sourceRefs.map((item, index) => validateSourceRef(item, `Memory atom.sourceRefs[${index}]`));
388
+ assertNonEmptyString(value.observationId, "Memory atom.observationId");
389
+ assertStringArray(value.sessionRefs, "Memory atom.sessionRefs");
390
+ assertStringArray(value.agentInstanceRefs, "Memory atom.agentInstanceRefs");
391
+ assertStringArray(value.projectRefs, "Memory atom.projectRefs");
392
+ assertStringArray(value.subjectRefs, "Memory atom.subjectRefs");
393
+ if (!Array.isArray(value.facets))
394
+ throw new Error("Memory atom.facets must be an array");
395
+ const facets = value.facets.map((item, index) => validateFacet(item, `Memory atom.facets[${index}]`));
396
+ if (!Array.isArray(value.relations))
397
+ throw new Error("Memory atom.relations must be an array");
398
+ const relations = value.relations.map((item, index) => validateRelation(item, `Memory atom.relations[${index}]`));
399
+ const provenance = value.provenance === undefined ? undefined : validateProvenance(value.provenance, "Memory atom.provenance");
400
+ assertUnitInterval(value.confidence, "Memory atom.confidence");
401
+ assertUnitInterval(value.importance, "Memory atom.importance");
402
+ const applicability = value.applicability === undefined
403
+ ? undefined
404
+ : validateApplicability(value.applicability, "Memory atom.applicability");
405
+ if (!MEMORY_EVIDENCE_CLASSES_V1.includes(value.evidenceClass)) {
406
+ throw new Error("Memory atom.evidenceClass is invalid");
407
+ }
408
+ assertNonEmptyString(value.contentRevision, "Memory atom.contentRevision");
409
+ assertNonEmptyString(value.writeReason, "Memory atom.writeReason");
410
+ return detachedFreeze({
411
+ memoryId: value.memoryId,
412
+ contractVersion: value.contractVersion,
413
+ schemaVersion: value.schemaVersion,
414
+ scope: value.scope,
415
+ retentionMode: value.retentionMode,
416
+ owner: value.owner,
417
+ profileId: value.profileId,
418
+ ...(value.shareGroupId === undefined ? {} : { shareGroupId: value.shareGroupId }),
419
+ ...(value.suiteId === undefined || value.suiteId === null ? {} : { suiteId: value.suiteId }),
420
+ retentionPolicyVersion: value.retentionPolicyVersion,
421
+ memoryKind: value.memoryKind,
422
+ payload: value.payload,
423
+ ...(preference === undefined ? {} : { preference }),
424
+ occurredAt: value.occurredAt,
425
+ recordedAt: value.recordedAt,
426
+ sourceRefs: Object.freeze(sourceRefs),
427
+ observationId: value.observationId,
428
+ sessionRefs: Object.freeze([...value.sessionRefs]),
429
+ agentInstanceRefs: Object.freeze([...value.agentInstanceRefs]),
430
+ projectRefs: Object.freeze([...value.projectRefs]),
431
+ subjectRefs: Object.freeze([...value.subjectRefs]),
432
+ facets: Object.freeze(facets),
433
+ relations: Object.freeze(relations),
434
+ ...(provenance === undefined ? {} : { provenance }),
435
+ confidence: value.confidence,
436
+ importance: value.importance,
437
+ ...(applicability === undefined ? {} : { applicability }),
438
+ evidenceClass: value.evidenceClass,
439
+ contentRevision: value.contentRevision,
440
+ writeReason: value.writeReason,
441
+ });
442
+ }
443
+ /** Builds a detached, frozen canonical atom (validate + freeze). */
444
+ export function buildMemoryAtomV1(atom) {
445
+ return validateMemoryAtomV1(atom);
446
+ }
447
+ /** FNV-1a 64-bit digest over the stable JSON serialization of `value`. */
448
+ export function memoryContentDigest(value) {
449
+ const serialized = stableStringify(value);
450
+ let digest = 14695981039346656037n;
451
+ for (const byte of new TextEncoder().encode(serialized)) {
452
+ digest ^= BigInt(byte);
453
+ digest = BigInt.asUintN(64, digest * 1099511628211n);
454
+ }
455
+ return digest.toString(16).padStart(16, "0");
456
+ }
457
+ /** Idempotent submission identity: the same (owner, observationId) is one observation. */
458
+ export function memoryObservationKey(owner, observationId) {
459
+ assertNonEmptyString(owner, "owner");
460
+ assertNonEmptyString(observationId, "observationId");
461
+ return `${owner}\u0000${observationId}`;
462
+ }
463
+ /**
464
+ * Suite-scoped visibility of one atom (方案系统设计 §6.1/§11):
465
+ * - `atom.suiteId === suiteId` → visible;
466
+ * - an explicitly promoted user-default preference (scope.level
467
+ * "user-default" WITH `confirmedAt` — the canonical explicit-confirmation
468
+ * marker set only by the explicit promotion channel) ignores the suiteId
469
+ * filter and is visible in every suite (§6.1 跨方案偏好);
470
+ * - absent suiteId (legacy/存量) → invisible to every suite (fail-safe
471
+ * default), counted via {@link MemorySuiteFilterStatsV1};
472
+ * - any other suiteId → invisible.
473
+ */
474
+ export function memorySuiteVisibility(atom, suiteId) {
475
+ assertNonEmptyString(suiteId, "suiteId");
476
+ if (atom.memoryKind === "preference" &&
477
+ atom.preference?.scope.level === "user-default" &&
478
+ atom.preference.confirmedAt !== undefined) {
479
+ return "visible";
480
+ }
481
+ if (atom.suiteId === undefined)
482
+ return "legacy";
483
+ if (atom.suiteId === suiteId)
484
+ return "visible";
485
+ return "foreign-suite";
486
+ }
487
+ //# sourceMappingURL=foundation.js.map