@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,100 @@
1
+ import { memoryObservationKey, validateMemoryAtomV1 } from "./foundation.js";
2
+ /** Creates the deterministic candidate state machine over a 1C.1b store. */
3
+ export function createMemoryCandidateMachine(options) {
4
+ const store = options.store;
5
+ const now = options.now ?? (() => Date.now());
6
+ const records = new Map();
7
+ const keyOf = (owner, observationId) => memoryObservationKey(owner, observationId);
8
+ const appendTransition = (record, to, patch = {}, reason) => {
9
+ const transition = Object.freeze({
10
+ from: record.state,
11
+ to,
12
+ at: now(),
13
+ ...(reason === undefined ? {} : { reason }),
14
+ });
15
+ const next = Object.freeze({
16
+ ...record,
17
+ ...patch,
18
+ state: to,
19
+ transitions: Object.freeze([...record.transitions, transition]),
20
+ });
21
+ records.set(record.key, next);
22
+ return next;
23
+ };
24
+ return {
25
+ observe(input) {
26
+ assertNonEmptyString(input.owner, "observation owner");
27
+ assertNonEmptyString(input.observationId, "observation observationId");
28
+ const key = keyOf(input.owner, input.observationId);
29
+ const existing = records.get(key);
30
+ if (existing)
31
+ return { record: existing, deduped: true };
32
+ const at = now();
33
+ const initial = Object.freeze({
34
+ key,
35
+ owner: input.owner,
36
+ observationId: input.observationId,
37
+ state: "observed",
38
+ transitions: Object.freeze([Object.freeze({ from: null, to: "observed", at })]),
39
+ });
40
+ // Structural gate 1 — identity consistency: the draft must claim the
41
+ // same observationId it is submitted under.
42
+ if (input.draft.observationId !== input.observationId) {
43
+ const rejected = appendTransition(initial, "rejected", { rejectedReason: "Draft atom observationId does not match the observation identity" }, "observation identity mismatch");
44
+ return { record: rejected, deduped: false };
45
+ }
46
+ // Structural gate 2 — full canonical atom validation (schema, sources,
47
+ // revisions, enums, JSON safety). Failures are explicit rejections.
48
+ let atom;
49
+ try {
50
+ atom = validateMemoryAtomV1(input.draft);
51
+ if (atom.owner !== input.owner)
52
+ throw new Error("Draft atom owner does not match the observation owner");
53
+ }
54
+ catch (error) {
55
+ // v8 ignore next -- 校验器只抛 Error 对象
56
+ const reason = error instanceof Error ? error.message : String(error);
57
+ const rejected = appendTransition(initial, "rejected", { rejectedReason: reason }, reason);
58
+ return { record: rejected, deduped: false };
59
+ }
60
+ const candidate = appendTransition(initial, "candidate", { atom });
61
+ return { record: candidate, deduped: false };
62
+ },
63
+ accept(observationId, owner, acceptOptions) {
64
+ const key = keyOf(owner, observationId);
65
+ const record = records.get(key);
66
+ if (!record)
67
+ throw new Error(`Unknown observation: ${observationId}`);
68
+ if (record.state === "committed" && record.committed) {
69
+ // Idempotent accept: the store commit is never repeated.
70
+ return record.committed;
71
+ }
72
+ if (record.state !== "candidate" || !record.atom) {
73
+ throw new Error(`Observation ${observationId} is ${record.state} and cannot be accepted`);
74
+ }
75
+ const committed = store.commit(record.atom, acceptOptions);
76
+ appendTransition(record, "committed", { committed });
77
+ return committed;
78
+ },
79
+ reject(observationId, owner, reason) {
80
+ assertNonEmptyString(reason, "reject reason");
81
+ const key = keyOf(owner, observationId);
82
+ const record = records.get(key);
83
+ if (!record)
84
+ throw new Error(`Unknown observation: ${observationId}`);
85
+ if (record.state === "committed")
86
+ throw new Error("A committed memory cannot be rejected");
87
+ if (record.state === "rejected")
88
+ return record;
89
+ return appendTransition(record, "rejected", { rejectedReason: reason }, reason);
90
+ },
91
+ stateOf(observationId, owner) {
92
+ return records.get(keyOf(owner, observationId));
93
+ },
94
+ };
95
+ }
96
+ function assertNonEmptyString(value, label) {
97
+ if (typeof value !== "string" || value.trim().length === 0)
98
+ throw new Error(`${label} must be a non-empty string`);
99
+ }
100
+ //# sourceMappingURL=candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../../src/memory/candidates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — minimal observation/candidate/commit state machine\n * (1C.2).\n *\n * The Foundation validates sources, revisions, idempotency, and structural\n * state only. Accept/reject are public state transitions recorded in an\n * immutable transition log; nothing is silently dropped. Idempotency identity\n * is `(owner, observationId)`: re-observing the same key returns the existing\n * record and never duplicates a commit.\n *\n * States: observed → candidate → committed, or → rejected (terminal, with\n * reason). Lifecycle beyond commit (decay/archive/purge) belongs to the\n * lifecycle manager slices.\n */\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryAtomV1, MemoryScopeV1 } from \"./foundation.ts\";\nimport { memoryObservationKey, validateMemoryAtomV1 } from \"./foundation.ts\";\nimport type { CommittedMemoryV1, MemoryStoreV1 } from \"./store.ts\";\n\nexport type MemoryCandidateStateV1 = \"observed\" | \"candidate\" | \"committed\" | \"rejected\";\n\nexport interface MemoryObservationInputV1<TPayload extends JsonValue = JsonValue> {\n\treadonly owner: string;\n\treadonly observationId: string;\n\t/** The unvalidated draft atom; its `observationId` field must match the observation. */\n\treadonly draft: MemoryAtomV1<TPayload>;\n}\n\nexport interface MemoryStateTransitionV1 {\n\treadonly from: MemoryCandidateStateV1 | null;\n\treadonly to: MemoryCandidateStateV1;\n\treadonly at: number;\n\treadonly reason?: string;\n}\n\nexport interface MemoryCandidateRecordV1<TPayload extends JsonValue = JsonValue> {\n\treadonly key: string;\n\treadonly owner: string;\n\treadonly observationId: string;\n\treadonly state: MemoryCandidateStateV1;\n\t/** Set from `candidate` onward: the validated, frozen draft. */\n\treadonly atom?: MemoryAtomV1<TPayload>;\n\t/** Set once committed: the store-assigned record. */\n\treadonly committed?: CommittedMemoryV1<TPayload>;\n\t/** Set when rejected: the structural reason the draft failed. */\n\treadonly rejectedReason?: string;\n\treadonly transitions: readonly MemoryStateTransitionV1[];\n}\n\nexport interface MemoryObserveResultV1<TPayload extends JsonValue = JsonValue> {\n\treadonly record: MemoryCandidateRecordV1<TPayload>;\n\t/** True when the (owner, observationId) identity already existed. */\n\treadonly deduped: boolean;\n}\n\nexport interface MemoryCandidateMachineV1 {\n\tobserve<TPayload extends JsonValue = JsonValue>(\n\t\tinput: MemoryObservationInputV1<TPayload>,\n\t): MemoryObserveResultV1<TPayload>;\n\t/** Promotes a candidate into the Foundation ledger (1C.1b store). */\n\taccept(observationId: string, owner: string, options?: { readonly retentionModeId?: string }): CommittedMemoryV1;\n\t/** Explicitly rejects a candidate; the reason becomes part of the audit log. */\n\treject(observationId: string, owner: string, reason: string): MemoryCandidateRecordV1;\n\tstateOf(observationId: string, owner: string): MemoryCandidateRecordV1 | undefined;\n}\n\n/** Creates the deterministic candidate state machine over a 1C.1b store. */\nexport function createMemoryCandidateMachine(options: {\n\treadonly store: MemoryStoreV1;\n\treadonly now?: () => number;\n}): MemoryCandidateMachineV1 {\n\tconst store = options.store;\n\tconst now = options.now ?? (() => Date.now());\n\tconst records = new Map<string, MemoryCandidateRecordV1<JsonValue>>();\n\n\tconst keyOf = (owner: string, observationId: string): string => memoryObservationKey(owner, observationId);\n\n\tconst appendTransition = (\n\t\trecord: MemoryCandidateRecordV1<JsonValue>,\n\t\tto: MemoryCandidateStateV1,\n\t\tpatch: Partial<MemoryCandidateRecordV1<JsonValue>> = {},\n\t\treason?: string,\n\t): MemoryCandidateRecordV1<JsonValue> => {\n\t\tconst transition: MemoryStateTransitionV1 = Object.freeze({\n\t\t\tfrom: record.state,\n\t\t\tto,\n\t\t\tat: now(),\n\t\t\t...(reason === undefined ? {} : { reason }),\n\t\t});\n\t\tconst next: MemoryCandidateRecordV1<JsonValue> = Object.freeze({\n\t\t\t...record,\n\t\t\t...patch,\n\t\t\tstate: to,\n\t\t\ttransitions: Object.freeze([...record.transitions, transition]),\n\t\t});\n\t\trecords.set(record.key, next);\n\t\treturn next;\n\t};\n\n\treturn {\n\t\tobserve<TPayload extends JsonValue = JsonValue>(\n\t\t\tinput: MemoryObservationInputV1<TPayload>,\n\t\t): MemoryObserveResultV1<TPayload> {\n\t\t\tassertNonEmptyString(input.owner, \"observation owner\");\n\t\t\tassertNonEmptyString(input.observationId, \"observation observationId\");\n\t\t\tconst key = keyOf(input.owner, input.observationId);\n\t\t\tconst existing = records.get(key);\n\t\t\tif (existing) return { record: existing as MemoryCandidateRecordV1<TPayload>, deduped: true };\n\n\t\t\tconst at = now();\n\t\t\tconst initial: MemoryCandidateRecordV1<TPayload> = Object.freeze({\n\t\t\t\tkey,\n\t\t\t\towner: input.owner,\n\t\t\t\tobservationId: input.observationId,\n\t\t\t\tstate: \"observed\" as const,\n\t\t\t\ttransitions: Object.freeze([Object.freeze({ from: null, to: \"observed\" as const, at })]),\n\t\t\t});\n\n\t\t\t// Structural gate 1 — identity consistency: the draft must claim the\n\t\t\t// same observationId it is submitted under.\n\t\t\tif (input.draft.observationId !== input.observationId) {\n\t\t\t\tconst rejected = appendTransition(\n\t\t\t\t\tinitial,\n\t\t\t\t\t\"rejected\",\n\t\t\t\t\t{ rejectedReason: \"Draft atom observationId does not match the observation identity\" },\n\t\t\t\t\t\"observation identity mismatch\",\n\t\t\t\t);\n\t\t\t\treturn { record: rejected as MemoryCandidateRecordV1<TPayload>, deduped: false };\n\t\t\t}\n\n\t\t\t// Structural gate 2 — full canonical atom validation (schema, sources,\n\t\t\t// revisions, enums, JSON safety). Failures are explicit rejections.\n\t\t\tlet atom: MemoryAtomV1<TPayload>;\n\t\t\ttry {\n\t\t\t\tatom = validateMemoryAtomV1<TPayload>(input.draft);\n\t\t\t\tif (atom.owner !== input.owner) throw new Error(\"Draft atom owner does not match the observation owner\");\n\t\t\t} catch (error) {\n\t\t\t\t// v8 ignore next -- 校验器只抛 Error 对象\n\t\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\t\tconst rejected = appendTransition(initial, \"rejected\", { rejectedReason: reason }, reason);\n\t\t\t\treturn { record: rejected as MemoryCandidateRecordV1<TPayload>, deduped: false };\n\t\t\t}\n\n\t\t\tconst candidate = appendTransition(initial, \"candidate\", { atom });\n\t\t\treturn { record: candidate as MemoryCandidateRecordV1<TPayload>, deduped: false };\n\t\t},\n\n\t\taccept(observationId, owner, acceptOptions) {\n\t\t\tconst key = keyOf(owner, observationId);\n\t\t\tconst record = records.get(key);\n\t\t\tif (!record) throw new Error(`Unknown observation: ${observationId}`);\n\t\t\tif (record.state === \"committed\" && record.committed) {\n\t\t\t\t// Idempotent accept: the store commit is never repeated.\n\t\t\t\treturn record.committed;\n\t\t\t}\n\t\t\tif (record.state !== \"candidate\" || !record.atom) {\n\t\t\t\tthrow new Error(`Observation ${observationId} is ${record.state} and cannot be accepted`);\n\t\t\t}\n\t\t\tconst committed = store.commit(record.atom, acceptOptions);\n\t\t\tappendTransition(record, \"committed\", { committed });\n\t\t\treturn committed;\n\t\t},\n\n\t\treject(observationId, owner, reason) {\n\t\t\tassertNonEmptyString(reason, \"reject reason\");\n\t\t\tconst key = keyOf(owner, observationId);\n\t\t\tconst record = records.get(key);\n\t\t\tif (!record) throw new Error(`Unknown observation: ${observationId}`);\n\t\t\tif (record.state === \"committed\") throw new Error(\"A committed memory cannot be rejected\");\n\t\t\tif (record.state === \"rejected\") return record;\n\t\t\treturn appendTransition(record, \"rejected\", { rejectedReason: reason }, reason);\n\t\t},\n\n\t\tstateOf(observationId, owner) {\n\t\t\treturn records.get(keyOf(owner, observationId)) as MemoryCandidateRecordV1 | undefined;\n\t\t},\n\t};\n}\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n\nexport type { MemoryScopeV1 };\n"]}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * code-memory agent (2.4a) — project facts, decisions, and tool/test facts
3
+ * recorded through the 1C Memory Foundation.
4
+ *
5
+ * Selective writes go through the candidate machine (validation, idempotent
6
+ * `(owner, observationId)` dedupe, explicit rejection); recall reuses the
7
+ * bounded recall index and packet. Every fact carries its source references,
8
+ * a `code-memory:kind` facet, and Foundation-managed scope/revisions, so a
9
+ * plain store replay restores full recall. Preference envelopes, complex
10
+ * semantics, and advanced indexing are later slices (2.4c/d).
11
+ */
12
+ import type { JsonValue } from "@agent-forge/plugin-sdk";
13
+ import type { MemoryCandidateMachineV1 } from "./candidates.ts";
14
+ import { type MemoryAtomV1, type MemorySourceRefV1 } from "./foundation.ts";
15
+ import type { MemoryRecallPacketV1 } from "./recall-packet.ts";
16
+ import { type MemoryRecallBudgetContributionV1, type MemoryRecallBudgetsV1 } from "./recall-packet.ts";
17
+ /** The slice of the 1C recall index the code-memory agent depends on. */
18
+ export interface MemoryRecallIndexShimV1 {
19
+ query(query: {
20
+ readonly owner: string;
21
+ readonly projects?: readonly string[];
22
+ readonly tasks?: readonly string[];
23
+ readonly facets?: readonly {
24
+ readonly namespace: string;
25
+ readonly key?: string;
26
+ readonly value?: string;
27
+ }[];
28
+ }): {
29
+ readonly items: readonly {
30
+ readonly atom: MemoryAtomV1<JsonValue>;
31
+ }[];
32
+ readonly total: number;
33
+ };
34
+ }
35
+ export type CodeMemoryFactKindV1 = "project" | "decision" | "tool_test" | "bug" | "task" | "compaction";
36
+ export interface CodeMemoryFactInputV1 {
37
+ readonly owner: string;
38
+ readonly kind: CodeMemoryFactKindV1;
39
+ /** Human-readable fact statement; must be non-empty. */
40
+ readonly statement: string;
41
+ /** Structured detail (command, exit code, test counts, decision options…). */
42
+ readonly detail?: JsonValue;
43
+ readonly project?: string;
44
+ readonly task?: string;
45
+ /** Where the fact came from (tool result, file, user answer…); at least one required. */
46
+ readonly sourceRefs: readonly {
47
+ readonly kind: MemorySourceRefV1["kind"];
48
+ readonly id: string;
49
+ }[];
50
+ readonly occurredAt: string;
51
+ /** Idempotency identity together with `owner`. */
52
+ readonly observationId: string;
53
+ readonly retentionMode?: "short" | "standard" | "long" | "permanent";
54
+ /**
55
+ * Suite (方案) to bind the fact to (方案系统设计 §6.1, M5). When absent the
56
+ * fact stays suite-less: readable through suite-unscoped queries only —
57
+ * suite-scoped reads skip it fail-safe.
58
+ */
59
+ readonly suiteId?: string;
60
+ }
61
+ export type CodeMemoryRecordResultV1 = {
62
+ readonly status: "recorded";
63
+ readonly memoryId: string;
64
+ readonly storeRevision: number;
65
+ } | {
66
+ readonly status: "deduped";
67
+ readonly memoryId: string;
68
+ } | {
69
+ readonly status: "rejected";
70
+ readonly reason: string;
71
+ };
72
+ export interface CodeMemoryRecallInputV1 {
73
+ readonly owner: string;
74
+ readonly kind?: CodeMemoryFactKindV1;
75
+ readonly project?: string;
76
+ readonly task?: string;
77
+ readonly requestedBudgets?: MemoryRecallBudgetContributionV1;
78
+ }
79
+ export interface CodeMemoryAgentV1 {
80
+ recordFact(input: CodeMemoryFactInputV1): CodeMemoryRecordResultV1;
81
+ recall(input: CodeMemoryRecallInputV1): MemoryRecallPacketV1;
82
+ }
83
+ export declare function createCodeMemoryAgent(options: {
84
+ readonly machine: MemoryCandidateMachineV1;
85
+ readonly index: MemoryRecallIndexShimV1;
86
+ readonly policyDefaults: MemoryRecallBudgetsV1;
87
+ readonly hostLimits: MemoryRecallBudgetsV1;
88
+ }): CodeMemoryAgentV1;
89
+ //# sourceMappingURL=code-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-memory.d.ts","sourceRoot":"","sources":["../../src/memory/code-memory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * code-memory agent (2.4a) — project facts, decisions, and tool/test facts\n * recorded through the 1C Memory Foundation.\n *\n * Selective writes go through the candidate machine (validation, idempotent\n * `(owner, observationId)` dedupe, explicit rejection); recall reuses the\n * bounded recall index and packet. Every fact carries its source references,\n * a `code-memory:kind` facet, and Foundation-managed scope/revisions, so a\n * plain store replay restores full recall. Preference envelopes, complex\n * semantics, and advanced indexing are later slices (2.4c/d).\n */\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryCandidateMachineV1 } from \"./candidates.ts\";\nimport { buildMemoryAtomV1, type MemoryAtomV1, type MemorySourceRefV1 } from \"./foundation.ts\";\nimport type { MemoryRecallPacketV1 } from \"./recall-packet.ts\";\nimport {\n\tbuildMemoryRecallPacketV1,\n\ttype MemoryRecallBudgetContributionV1,\n\ttype MemoryRecallBudgetsV1,\n\tresolveMemoryRecallBudgets,\n} from \"./recall-packet.ts\";\nimport { MEMORY_CONTRACT_VERSION_V2 } from \"./transfer.ts\";\n\n/** The slice of the 1C recall index the code-memory agent depends on. */\nexport interface MemoryRecallIndexShimV1 {\n\tquery(query: {\n\t\treadonly owner: string;\n\t\treadonly projects?: readonly string[];\n\t\treadonly tasks?: readonly string[];\n\t\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t}): { readonly items: readonly { readonly atom: MemoryAtomV1<JsonValue> }[]; readonly total: number };\n}\n\nexport type CodeMemoryFactKindV1 = \"project\" | \"decision\" | \"tool_test\" | \"bug\" | \"task\" | \"compaction\";\n\nexport interface CodeMemoryFactInputV1 {\n\treadonly owner: string;\n\treadonly kind: CodeMemoryFactKindV1;\n\t/** Human-readable fact statement; must be non-empty. */\n\treadonly statement: string;\n\t/** Structured detail (command, exit code, test counts, decision options…). */\n\treadonly detail?: JsonValue;\n\treadonly project?: string;\n\treadonly task?: string;\n\t/** Where the fact came from (tool result, file, user answer…); at least one required. */\n\treadonly sourceRefs: readonly { readonly kind: MemorySourceRefV1[\"kind\"]; readonly id: string }[];\n\treadonly occurredAt: string;\n\t/** Idempotency identity together with `owner`. */\n\treadonly observationId: string;\n\treadonly retentionMode?: \"short\" | \"standard\" | \"long\" | \"permanent\";\n\t/**\n\t * Suite (方案) to bind the fact to (方案系统设计 §6.1, M5). When absent the\n\t * fact stays suite-less: readable through suite-unscoped queries only —\n\t * suite-scoped reads skip it fail-safe.\n\t */\n\treadonly suiteId?: string;\n}\n\nexport type CodeMemoryRecordResultV1 =\n\t| { readonly status: \"recorded\"; readonly memoryId: string; readonly storeRevision: number }\n\t| { readonly status: \"deduped\"; readonly memoryId: string }\n\t| { readonly status: \"rejected\"; readonly reason: string };\n\nexport interface CodeMemoryRecallInputV1 {\n\treadonly owner: string;\n\treadonly kind?: CodeMemoryFactKindV1;\n\treadonly project?: string;\n\treadonly task?: string;\n\treadonly requestedBudgets?: MemoryRecallBudgetContributionV1;\n}\n\nexport interface CodeMemoryAgentV1 {\n\trecordFact(input: CodeMemoryFactInputV1): CodeMemoryRecordResultV1;\n\trecall(input: CodeMemoryRecallInputV1): MemoryRecallPacketV1;\n}\n\nconst CONTRACT_VERSION = MEMORY_CONTRACT_VERSION_V2;\nconst RETENTION_POLICY_VERSION = \"retention@1\";\n\n/** FNV-1a 32-bit — deterministic identity from the given value. */\nfunction identityHash(value: string): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let index = 0; index < value.length; index += 1) {\n\t\thash ^= value.charCodeAt(index);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn (hash >>> 0).toString(36);\n}\n\nexport function createCodeMemoryAgent(options: {\n\treadonly machine: MemoryCandidateMachineV1;\n\treadonly index: MemoryRecallIndexShimV1;\n\treadonly policyDefaults: MemoryRecallBudgetsV1;\n\treadonly hostLimits: MemoryRecallBudgetsV1;\n}): CodeMemoryAgentV1 {\n\tconst evidenceClassOf = (kind: CodeMemoryFactKindV1) =>\n\t\tkind === \"tool_test\" || kind === \"bug\"\n\t\t\t? (\"tool_or_test\" as const)\n\t\t\t: kind === \"compaction\"\n\t\t\t\t? (\"derived\" as const)\n\t\t\t\t: (\"explicit\" as const);\n\n\treturn {\n\t\trecordFact(input) {\n\t\t\tif (typeof input.statement !== \"string\" || input.statement.trim().length === 0) {\n\t\t\t\treturn { status: \"rejected\", reason: \"code-memory statement must be a non-empty string\" };\n\t\t\t}\n\t\t\tif (!Array.isArray(input.sourceRefs) || input.sourceRefs.length === 0) {\n\t\t\t\treturn { status: \"rejected\", reason: \"code-memory facts require at least one source reference\" };\n\t\t\t}\n\t\t\tif (Number.isNaN(Date.parse(input.occurredAt))) {\n\t\t\t\treturn { status: \"rejected\", reason: \"code-memory occurredAt must be an ISO-8601 timestamp\" };\n\t\t\t}\n\t\t\tconst memoryId = `code-${identityHash(`${input.owner}\\u0000${input.observationId}`)}`;\n\t\t\tconst sourceRefs: readonly MemorySourceRefV1[] = input.sourceRefs.map((ref) =>\n\t\t\t\tObject.freeze({ kind: ref.kind, id: ref.id }),\n\t\t\t);\n\t\t\tconst draft = buildMemoryAtomV1({\n\t\t\t\tmemoryId,\n\t\t\t\tcontractVersion: CONTRACT_VERSION,\n\t\t\t\tschemaVersion: 1,\n\t\t\t\tscope: \"long-term\",\n\t\t\t\tretentionMode: input.retentionMode ?? \"standard\",\n\t\t\t\towner: input.owner,\n\t\t\t\tprofileId: \"code-memory\",\n\t\t\t\t...(input.suiteId === undefined ? {} : { suiteId: input.suiteId }),\n\t\t\t\tretentionPolicyVersion: RETENTION_POLICY_VERSION,\n\t\t\t\tmemoryKind: \"fact\",\n\t\t\t\tpayload: {\n\t\t\t\t\tkind: input.kind,\n\t\t\t\t\tstatement: input.statement,\n\t\t\t\t\t...(input.detail === undefined ? {} : { detail: input.detail }),\n\t\t\t\t} as JsonValue,\n\t\t\t\toccurredAt: input.occurredAt,\n\t\t\t\trecordedAt: input.occurredAt,\n\t\t\t\tsourceRefs,\n\t\t\t\tobservationId: input.observationId,\n\t\t\t\tsessionRefs: [],\n\t\t\t\tagentInstanceRefs: [],\n\t\t\t\tprojectRefs: input.project === undefined ? [] : [input.project],\n\t\t\t\tsubjectRefs: input.task === undefined ? [] : [input.task],\n\t\t\t\tfacets: [{ namespace: \"code-memory\", schemaVersion: 1, key: \"kind\", value: input.kind }],\n\t\t\t\trelations: [],\n\t\t\t\tconfidence: 1,\n\t\t\t\timportance: 0.5,\n\t\t\t\tevidenceClass: evidenceClassOf(input.kind),\n\t\t\t\tcontentRevision: `c-${identityHash(JSON.stringify([input.statement, input.detail ?? null]))}`,\n\t\t\t\twriteReason: `code-memory/${input.kind}`,\n\t\t\t});\n\n\t\t\tconst observation = options.machine.observe({\n\t\t\t\towner: input.owner,\n\t\t\t\tobservationId: input.observationId,\n\t\t\t\tdraft,\n\t\t\t});\n\t\t\t// v8 ignore next -- recordFact 先本地校验并构造合法 draft,observe 不可能拒绝\n\t\t\tif (observation.record.state === \"rejected\") {\n\t\t\t\t// v8 ignore next -- 同上:该分支不可达\n\t\t\t\treturn { status: \"rejected\", reason: observation.record.rejectedReason ?? \"rejected\" };\n\t\t\t}\n\t\t\tif (observation.record.state === \"committed\" && observation.record.committed) {\n\t\t\t\treturn { status: \"deduped\", memoryId: observation.record.committed.atom.memoryId };\n\t\t\t}\n\t\t\tconst committed = options.machine.accept(input.observationId, input.owner);\n\t\t\treturn { status: \"recorded\", memoryId: committed.atom.memoryId, storeRevision: committed.storeRevision };\n\t\t},\n\t\trecall(input) {\n\t\t\tconst budgets = resolveMemoryRecallBudgets({\n\t\t\t\trequested: input.requestedBudgets,\n\t\t\t\tpolicyDefaults: options.policyDefaults,\n\t\t\t\thostLimits: options.hostLimits,\n\t\t\t}).budgets;\n\t\t\tconst page = options.index.query({\n\t\t\t\towner: input.owner,\n\t\t\t\t...(input.project === undefined ? {} : { projects: [input.project] }),\n\t\t\t\t...(input.task === undefined ? {} : { tasks: [input.task] }),\n\t\t\t\t...(input.kind === undefined\n\t\t\t\t\t? { facets: [{ namespace: \"code-memory\" }] }\n\t\t\t\t\t: { facets: [{ namespace: \"code-memory\", key: \"kind\", value: input.kind }] }),\n\t\t\t});\n\t\t\treturn buildMemoryRecallPacketV1({\n\t\t\t\toperationId: `code-memory-recall-${identityHash(\n\t\t\t\t\tJSON.stringify([input.owner, input.kind ?? \"*\", input.project ?? \"*\", input.task ?? \"*\"]),\n\t\t\t\t)}`,\n\t\t\t\tcandidates: page.items.map((item) => item.atom),\n\t\t\t\tbudgets,\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,104 @@
1
+ import { buildMemoryAtomV1 } from "./foundation.js";
2
+ import { buildMemoryRecallPacketV1, resolveMemoryRecallBudgets, } from "./recall-packet.js";
3
+ import { MEMORY_CONTRACT_VERSION_V2 } from "./transfer.js";
4
+ const CONTRACT_VERSION = MEMORY_CONTRACT_VERSION_V2;
5
+ const RETENTION_POLICY_VERSION = "retention@1";
6
+ /** FNV-1a 32-bit — deterministic identity from the given value. */
7
+ function identityHash(value) {
8
+ let hash = 0x811c9dc5;
9
+ for (let index = 0; index < value.length; index += 1) {
10
+ hash ^= value.charCodeAt(index);
11
+ hash = Math.imul(hash, 0x01000193);
12
+ }
13
+ return (hash >>> 0).toString(36);
14
+ }
15
+ export function createCodeMemoryAgent(options) {
16
+ const evidenceClassOf = (kind) => kind === "tool_test" || kind === "bug"
17
+ ? "tool_or_test"
18
+ : kind === "compaction"
19
+ ? "derived"
20
+ : "explicit";
21
+ return {
22
+ recordFact(input) {
23
+ if (typeof input.statement !== "string" || input.statement.trim().length === 0) {
24
+ return { status: "rejected", reason: "code-memory statement must be a non-empty string" };
25
+ }
26
+ if (!Array.isArray(input.sourceRefs) || input.sourceRefs.length === 0) {
27
+ return { status: "rejected", reason: "code-memory facts require at least one source reference" };
28
+ }
29
+ if (Number.isNaN(Date.parse(input.occurredAt))) {
30
+ return { status: "rejected", reason: "code-memory occurredAt must be an ISO-8601 timestamp" };
31
+ }
32
+ const memoryId = `code-${identityHash(`${input.owner}\u0000${input.observationId}`)}`;
33
+ const sourceRefs = input.sourceRefs.map((ref) => Object.freeze({ kind: ref.kind, id: ref.id }));
34
+ const draft = buildMemoryAtomV1({
35
+ memoryId,
36
+ contractVersion: CONTRACT_VERSION,
37
+ schemaVersion: 1,
38
+ scope: "long-term",
39
+ retentionMode: input.retentionMode ?? "standard",
40
+ owner: input.owner,
41
+ profileId: "code-memory",
42
+ ...(input.suiteId === undefined ? {} : { suiteId: input.suiteId }),
43
+ retentionPolicyVersion: RETENTION_POLICY_VERSION,
44
+ memoryKind: "fact",
45
+ payload: {
46
+ kind: input.kind,
47
+ statement: input.statement,
48
+ ...(input.detail === undefined ? {} : { detail: input.detail }),
49
+ },
50
+ occurredAt: input.occurredAt,
51
+ recordedAt: input.occurredAt,
52
+ sourceRefs,
53
+ observationId: input.observationId,
54
+ sessionRefs: [],
55
+ agentInstanceRefs: [],
56
+ projectRefs: input.project === undefined ? [] : [input.project],
57
+ subjectRefs: input.task === undefined ? [] : [input.task],
58
+ facets: [{ namespace: "code-memory", schemaVersion: 1, key: "kind", value: input.kind }],
59
+ relations: [],
60
+ confidence: 1,
61
+ importance: 0.5,
62
+ evidenceClass: evidenceClassOf(input.kind),
63
+ contentRevision: `c-${identityHash(JSON.stringify([input.statement, input.detail ?? null]))}`,
64
+ writeReason: `code-memory/${input.kind}`,
65
+ });
66
+ const observation = options.machine.observe({
67
+ owner: input.owner,
68
+ observationId: input.observationId,
69
+ draft,
70
+ });
71
+ // v8 ignore next -- recordFact 先本地校验并构造合法 draft,observe 不可能拒绝
72
+ if (observation.record.state === "rejected") {
73
+ // v8 ignore next -- 同上:该分支不可达
74
+ return { status: "rejected", reason: observation.record.rejectedReason ?? "rejected" };
75
+ }
76
+ if (observation.record.state === "committed" && observation.record.committed) {
77
+ return { status: "deduped", memoryId: observation.record.committed.atom.memoryId };
78
+ }
79
+ const committed = options.machine.accept(input.observationId, input.owner);
80
+ return { status: "recorded", memoryId: committed.atom.memoryId, storeRevision: committed.storeRevision };
81
+ },
82
+ recall(input) {
83
+ const budgets = resolveMemoryRecallBudgets({
84
+ requested: input.requestedBudgets,
85
+ policyDefaults: options.policyDefaults,
86
+ hostLimits: options.hostLimits,
87
+ }).budgets;
88
+ const page = options.index.query({
89
+ owner: input.owner,
90
+ ...(input.project === undefined ? {} : { projects: [input.project] }),
91
+ ...(input.task === undefined ? {} : { tasks: [input.task] }),
92
+ ...(input.kind === undefined
93
+ ? { facets: [{ namespace: "code-memory" }] }
94
+ : { facets: [{ namespace: "code-memory", key: "kind", value: input.kind }] }),
95
+ });
96
+ return buildMemoryRecallPacketV1({
97
+ operationId: `code-memory-recall-${identityHash(JSON.stringify([input.owner, input.kind ?? "*", input.project ?? "*", input.task ?? "*"]))}`,
98
+ candidates: page.items.map((item) => item.atom),
99
+ budgets,
100
+ });
101
+ },
102
+ };
103
+ }
104
+ //# sourceMappingURL=code-memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-memory.js","sourceRoot":"","sources":["../../src/memory/code-memory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * code-memory agent (2.4a) — project facts, decisions, and tool/test facts\n * recorded through the 1C Memory Foundation.\n *\n * Selective writes go through the candidate machine (validation, idempotent\n * `(owner, observationId)` dedupe, explicit rejection); recall reuses the\n * bounded recall index and packet. Every fact carries its source references,\n * a `code-memory:kind` facet, and Foundation-managed scope/revisions, so a\n * plain store replay restores full recall. Preference envelopes, complex\n * semantics, and advanced indexing are later slices (2.4c/d).\n */\nimport type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryCandidateMachineV1 } from \"./candidates.ts\";\nimport { buildMemoryAtomV1, type MemoryAtomV1, type MemorySourceRefV1 } from \"./foundation.ts\";\nimport type { MemoryRecallPacketV1 } from \"./recall-packet.ts\";\nimport {\n\tbuildMemoryRecallPacketV1,\n\ttype MemoryRecallBudgetContributionV1,\n\ttype MemoryRecallBudgetsV1,\n\tresolveMemoryRecallBudgets,\n} from \"./recall-packet.ts\";\nimport { MEMORY_CONTRACT_VERSION_V2 } from \"./transfer.ts\";\n\n/** The slice of the 1C recall index the code-memory agent depends on. */\nexport interface MemoryRecallIndexShimV1 {\n\tquery(query: {\n\t\treadonly owner: string;\n\t\treadonly projects?: readonly string[];\n\t\treadonly tasks?: readonly string[];\n\t\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t}): { readonly items: readonly { readonly atom: MemoryAtomV1<JsonValue> }[]; readonly total: number };\n}\n\nexport type CodeMemoryFactKindV1 = \"project\" | \"decision\" | \"tool_test\" | \"bug\" | \"task\" | \"compaction\";\n\nexport interface CodeMemoryFactInputV1 {\n\treadonly owner: string;\n\treadonly kind: CodeMemoryFactKindV1;\n\t/** Human-readable fact statement; must be non-empty. */\n\treadonly statement: string;\n\t/** Structured detail (command, exit code, test counts, decision options…). */\n\treadonly detail?: JsonValue;\n\treadonly project?: string;\n\treadonly task?: string;\n\t/** Where the fact came from (tool result, file, user answer…); at least one required. */\n\treadonly sourceRefs: readonly { readonly kind: MemorySourceRefV1[\"kind\"]; readonly id: string }[];\n\treadonly occurredAt: string;\n\t/** Idempotency identity together with `owner`. */\n\treadonly observationId: string;\n\treadonly retentionMode?: \"short\" | \"standard\" | \"long\" | \"permanent\";\n\t/**\n\t * Suite (方案) to bind the fact to (方案系统设计 §6.1, M5). When absent the\n\t * fact stays suite-less: readable through suite-unscoped queries only —\n\t * suite-scoped reads skip it fail-safe.\n\t */\n\treadonly suiteId?: string;\n}\n\nexport type CodeMemoryRecordResultV1 =\n\t| { readonly status: \"recorded\"; readonly memoryId: string; readonly storeRevision: number }\n\t| { readonly status: \"deduped\"; readonly memoryId: string }\n\t| { readonly status: \"rejected\"; readonly reason: string };\n\nexport interface CodeMemoryRecallInputV1 {\n\treadonly owner: string;\n\treadonly kind?: CodeMemoryFactKindV1;\n\treadonly project?: string;\n\treadonly task?: string;\n\treadonly requestedBudgets?: MemoryRecallBudgetContributionV1;\n}\n\nexport interface CodeMemoryAgentV1 {\n\trecordFact(input: CodeMemoryFactInputV1): CodeMemoryRecordResultV1;\n\trecall(input: CodeMemoryRecallInputV1): MemoryRecallPacketV1;\n}\n\nconst CONTRACT_VERSION = MEMORY_CONTRACT_VERSION_V2;\nconst RETENTION_POLICY_VERSION = \"retention@1\";\n\n/** FNV-1a 32-bit — deterministic identity from the given value. */\nfunction identityHash(value: string): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let index = 0; index < value.length; index += 1) {\n\t\thash ^= value.charCodeAt(index);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn (hash >>> 0).toString(36);\n}\n\nexport function createCodeMemoryAgent(options: {\n\treadonly machine: MemoryCandidateMachineV1;\n\treadonly index: MemoryRecallIndexShimV1;\n\treadonly policyDefaults: MemoryRecallBudgetsV1;\n\treadonly hostLimits: MemoryRecallBudgetsV1;\n}): CodeMemoryAgentV1 {\n\tconst evidenceClassOf = (kind: CodeMemoryFactKindV1) =>\n\t\tkind === \"tool_test\" || kind === \"bug\"\n\t\t\t? (\"tool_or_test\" as const)\n\t\t\t: kind === \"compaction\"\n\t\t\t\t? (\"derived\" as const)\n\t\t\t\t: (\"explicit\" as const);\n\n\treturn {\n\t\trecordFact(input) {\n\t\t\tif (typeof input.statement !== \"string\" || input.statement.trim().length === 0) {\n\t\t\t\treturn { status: \"rejected\", reason: \"code-memory statement must be a non-empty string\" };\n\t\t\t}\n\t\t\tif (!Array.isArray(input.sourceRefs) || input.sourceRefs.length === 0) {\n\t\t\t\treturn { status: \"rejected\", reason: \"code-memory facts require at least one source reference\" };\n\t\t\t}\n\t\t\tif (Number.isNaN(Date.parse(input.occurredAt))) {\n\t\t\t\treturn { status: \"rejected\", reason: \"code-memory occurredAt must be an ISO-8601 timestamp\" };\n\t\t\t}\n\t\t\tconst memoryId = `code-${identityHash(`${input.owner}\\u0000${input.observationId}`)}`;\n\t\t\tconst sourceRefs: readonly MemorySourceRefV1[] = input.sourceRefs.map((ref) =>\n\t\t\t\tObject.freeze({ kind: ref.kind, id: ref.id }),\n\t\t\t);\n\t\t\tconst draft = buildMemoryAtomV1({\n\t\t\t\tmemoryId,\n\t\t\t\tcontractVersion: CONTRACT_VERSION,\n\t\t\t\tschemaVersion: 1,\n\t\t\t\tscope: \"long-term\",\n\t\t\t\tretentionMode: input.retentionMode ?? \"standard\",\n\t\t\t\towner: input.owner,\n\t\t\t\tprofileId: \"code-memory\",\n\t\t\t\t...(input.suiteId === undefined ? {} : { suiteId: input.suiteId }),\n\t\t\t\tretentionPolicyVersion: RETENTION_POLICY_VERSION,\n\t\t\t\tmemoryKind: \"fact\",\n\t\t\t\tpayload: {\n\t\t\t\t\tkind: input.kind,\n\t\t\t\t\tstatement: input.statement,\n\t\t\t\t\t...(input.detail === undefined ? {} : { detail: input.detail }),\n\t\t\t\t} as JsonValue,\n\t\t\t\toccurredAt: input.occurredAt,\n\t\t\t\trecordedAt: input.occurredAt,\n\t\t\t\tsourceRefs,\n\t\t\t\tobservationId: input.observationId,\n\t\t\t\tsessionRefs: [],\n\t\t\t\tagentInstanceRefs: [],\n\t\t\t\tprojectRefs: input.project === undefined ? [] : [input.project],\n\t\t\t\tsubjectRefs: input.task === undefined ? [] : [input.task],\n\t\t\t\tfacets: [{ namespace: \"code-memory\", schemaVersion: 1, key: \"kind\", value: input.kind }],\n\t\t\t\trelations: [],\n\t\t\t\tconfidence: 1,\n\t\t\t\timportance: 0.5,\n\t\t\t\tevidenceClass: evidenceClassOf(input.kind),\n\t\t\t\tcontentRevision: `c-${identityHash(JSON.stringify([input.statement, input.detail ?? null]))}`,\n\t\t\t\twriteReason: `code-memory/${input.kind}`,\n\t\t\t});\n\n\t\t\tconst observation = options.machine.observe({\n\t\t\t\towner: input.owner,\n\t\t\t\tobservationId: input.observationId,\n\t\t\t\tdraft,\n\t\t\t});\n\t\t\t// v8 ignore next -- recordFact 先本地校验并构造合法 draft,observe 不可能拒绝\n\t\t\tif (observation.record.state === \"rejected\") {\n\t\t\t\t// v8 ignore next -- 同上:该分支不可达\n\t\t\t\treturn { status: \"rejected\", reason: observation.record.rejectedReason ?? \"rejected\" };\n\t\t\t}\n\t\t\tif (observation.record.state === \"committed\" && observation.record.committed) {\n\t\t\t\treturn { status: \"deduped\", memoryId: observation.record.committed.atom.memoryId };\n\t\t\t}\n\t\t\tconst committed = options.machine.accept(input.observationId, input.owner);\n\t\t\treturn { status: \"recorded\", memoryId: committed.atom.memoryId, storeRevision: committed.storeRevision };\n\t\t},\n\t\trecall(input) {\n\t\t\tconst budgets = resolveMemoryRecallBudgets({\n\t\t\t\trequested: input.requestedBudgets,\n\t\t\t\tpolicyDefaults: options.policyDefaults,\n\t\t\t\thostLimits: options.hostLimits,\n\t\t\t}).budgets;\n\t\t\tconst page = options.index.query({\n\t\t\t\towner: input.owner,\n\t\t\t\t...(input.project === undefined ? {} : { projects: [input.project] }),\n\t\t\t\t...(input.task === undefined ? {} : { tasks: [input.task] }),\n\t\t\t\t...(input.kind === undefined\n\t\t\t\t\t? { facets: [{ namespace: \"code-memory\" }] }\n\t\t\t\t\t: { facets: [{ namespace: \"code-memory\", key: \"kind\", value: input.kind }] }),\n\t\t\t});\n\t\t\treturn buildMemoryRecallPacketV1({\n\t\t\t\toperationId: `code-memory-recall-${identityHash(\n\t\t\t\t\tJSON.stringify([input.owner, input.kind ?? \"*\", input.project ?? \"*\", input.task ?? \"*\"]),\n\t\t\t\t)}`,\n\t\t\t\tcandidates: page.items.map((item) => item.atom),\n\t\t\t\tbudgets,\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Memory Foundation — memory commit ordering across compaction (1C.7a).
3
+ *
4
+ * Adapts memory commits to the 1B.3c revision-checked session/context commit:
5
+ * - Memory is committed BEFORE compaction; a failed compaction can never
6
+ * delete already-committed memories.
7
+ * - Commits that arrive DURING a compaction are queued (projection-pending)
8
+ * and replayed in order when the compaction settles — success or failure.
9
+ * - The compaction commit and every memory commit carry an operationId and
10
+ * the context revision they are causally bound to; the journal makes that
11
+ * ordering observable.
12
+ * - After revoke/reload/dispose, late memory writes fail explicitly.
13
+ * - `abort` failure mode (D-027): the compaction failure is recorded and the
14
+ * queued memories replay — degraded-visible marking belongs to the Profile
15
+ * layer (2.x), not to this sequencer.
16
+ */
17
+ import type { MemoryAtomV1 } from "./foundation.ts";
18
+ import type { CommittedMemoryV1, MemoryStoreV1 } from "./store.ts";
19
+ export type MemorySequencerStateV1 = "idle" | "compacting" | "revoked";
20
+ export interface MemorySequencerJournalFactV1 {
21
+ readonly kind: "memory_commit" | "compaction_started" | "compaction_completed" | "compaction_failed";
22
+ readonly operationId: string;
23
+ readonly contextRevision: number;
24
+ readonly memoryId?: string;
25
+ readonly reason?: string;
26
+ readonly queued?: boolean;
27
+ readonly replayed?: boolean;
28
+ }
29
+ export interface MemorySequencedCommitV1 {
30
+ readonly committed: CommittedMemoryV1;
31
+ readonly contextRevision: number;
32
+ readonly operationId: string;
33
+ /** True when the commit was queued during a compaction and replayed after. */
34
+ readonly replayed?: boolean;
35
+ /** True when the commit is held until the running compaction settles. */
36
+ readonly projectionPending?: boolean;
37
+ }
38
+ export interface MemoryCompactionSequencerV1 {
39
+ commitMemory(input: {
40
+ readonly atom: MemoryAtomV1;
41
+ readonly operationId: string;
42
+ }): MemorySequencedCommitV1;
43
+ beginCompaction(input: {
44
+ readonly operationId: string;
45
+ readonly expectedContextRevision: number;
46
+ }): void;
47
+ completeCompaction(input: {
48
+ readonly operationId: string;
49
+ readonly newContextRevision?: number;
50
+ }): number;
51
+ failCompaction(input: {
52
+ readonly operationId: string;
53
+ readonly reason: string;
54
+ }): number;
55
+ setRevoked(reason: string): void;
56
+ readonly contextRevision: number;
57
+ pendingCommits(): number;
58
+ journal(): readonly MemorySequencerJournalFactV1[];
59
+ }
60
+ export declare function createMemoryCompactionSequencer(options: {
61
+ readonly store: MemoryStoreV1;
62
+ }): MemoryCompactionSequencerV1;
63
+ //# sourceMappingURL=compaction-sequencer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction-sequencer.d.ts","sourceRoot":"","sources":["../../src/memory/compaction-sequencer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnE,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;AAEvE,MAAM,WAAW,4BAA4B;IAC5C,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,oBAAoB,GAAG,sBAAsB,GAAG,mBAAmB,CAAC;IACrG,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,2BAA2B;IAC3C,YAAY,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;QAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,uBAAuB,CAAC;IAC5G,eAAe,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzG,kBAAkB,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC;IAC1G,cAAc,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC;IACzF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,cAAc,IAAI,MAAM,CAAC;IACzB,OAAO,IAAI,SAAS,4BAA4B,EAAE,CAAC;CACnD;AAED,wBAAgB,+BAA+B,CAAC,OAAO,EAAE;IACxD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;CAC9B,GAAG,2BAA2B,CAwI9B","sourcesContent":["/**\n * Memory Foundation — memory commit ordering across compaction (1C.7a).\n *\n * Adapts memory commits to the 1B.3c revision-checked session/context commit:\n * - Memory is committed BEFORE compaction; a failed compaction can never\n * delete already-committed memories.\n * - Commits that arrive DURING a compaction are queued (projection-pending)\n * and replayed in order when the compaction settles — success or failure.\n * - The compaction commit and every memory commit carry an operationId and\n * the context revision they are causally bound to; the journal makes that\n * ordering observable.\n * - After revoke/reload/dispose, late memory writes fail explicitly.\n * - `abort` failure mode (D-027): the compaction failure is recorded and the\n * queued memories replay — degraded-visible marking belongs to the Profile\n * layer (2.x), not to this sequencer.\n */\nimport type { MemoryAtomV1 } from \"./foundation.ts\";\nimport { validateMemoryAtomV1 } from \"./foundation.ts\";\nimport type { CommittedMemoryV1, MemoryStoreV1 } from \"./store.ts\";\n\nexport type MemorySequencerStateV1 = \"idle\" | \"compacting\" | \"revoked\";\n\nexport interface MemorySequencerJournalFactV1 {\n\treadonly kind: \"memory_commit\" | \"compaction_started\" | \"compaction_completed\" | \"compaction_failed\";\n\treadonly operationId: string;\n\treadonly contextRevision: number;\n\treadonly memoryId?: string;\n\treadonly reason?: string;\n\treadonly queued?: boolean;\n\treadonly replayed?: boolean;\n}\n\nexport interface MemorySequencedCommitV1 {\n\treadonly committed: CommittedMemoryV1;\n\treadonly contextRevision: number;\n\treadonly operationId: string;\n\t/** True when the commit was queued during a compaction and replayed after. */\n\treadonly replayed?: boolean;\n\t/** True when the commit is held until the running compaction settles. */\n\treadonly projectionPending?: boolean;\n}\n\nexport interface MemoryCompactionSequencerV1 {\n\tcommitMemory(input: { readonly atom: MemoryAtomV1; readonly operationId: string }): MemorySequencedCommitV1;\n\tbeginCompaction(input: { readonly operationId: string; readonly expectedContextRevision: number }): void;\n\tcompleteCompaction(input: { readonly operationId: string; readonly newContextRevision?: number }): number;\n\tfailCompaction(input: { readonly operationId: string; readonly reason: string }): number;\n\tsetRevoked(reason: string): void;\n\treadonly contextRevision: number;\n\tpendingCommits(): number;\n\tjournal(): readonly MemorySequencerJournalFactV1[];\n}\n\nexport function createMemoryCompactionSequencer(options: {\n\treadonly store: MemoryStoreV1;\n}): MemoryCompactionSequencerV1 {\n\tconst store = options.store;\n\tlet state: MemorySequencerStateV1 = \"idle\";\n\tlet contextRevision = 1;\n\tlet revokedReason: string | undefined;\n\tlet activeCompactionOperationId: string | undefined;\n\tconst pending: Array<{ atom: MemoryAtomV1; operationId: string }> = [];\n\tconst journalLog: MemorySequencerJournalFactV1[] = [];\n\n\tconst assertNotRevoked = (): void => {\n\t\tif (state === \"revoked\") {\n\t\t\t// v8 ignore next -- setRevoked 必带 reason\n\t\t\tthrow new Error(`Memory sequencer is revoked: ${revokedReason ?? \"revoked\"}; late writes must fail`);\n\t\t}\n\t};\n\n\tconst commitNow = (\n\t\tatom: MemoryAtomV1,\n\t\toperationId: string,\n\t\tflags: { replayed?: boolean } = {},\n\t): MemorySequencedCommitV1 => {\n\t\tconst validated = validateMemoryAtomV1(atom);\n\t\tconst committed = store.commit(validated);\n\t\tconst fact: MemorySequencerJournalFactV1 = Object.freeze({\n\t\t\tkind: \"memory_commit\",\n\t\t\toperationId,\n\t\t\tcontextRevision,\n\t\t\tmemoryId: committed.atom.memoryId,\n\t\t\t...(flags.replayed === undefined ? {} : { replayed: true }),\n\t\t});\n\t\tjournalLog.push(fact);\n\t\treturn Object.freeze({\n\t\t\tcommitted,\n\t\t\tcontextRevision,\n\t\t\toperationId,\n\t\t\t...(flags.replayed === undefined ? {} : { replayed: true }),\n\t\t});\n\t};\n\n\tconst replayPending = (compactionOperationId: string, failureReason?: string): number => {\n\t\tlet replayed = 0;\n\t\twhile (pending.length > 0) {\n\t\t\tconst entry = pending.shift()!;\n\t\t\tcommitNow(entry.atom, entry.operationId, { replayed: true });\n\t\t\treplayed += 1;\n\t\t}\n\t\tjournalLog.push(\n\t\t\tObject.freeze({\n\t\t\t\tkind: failureReason === undefined ? \"compaction_completed\" : \"compaction_failed\",\n\t\t\t\toperationId: compactionOperationId,\n\t\t\t\tcontextRevision,\n\t\t\t\t...(failureReason === undefined ? {} : { reason: failureReason }),\n\t\t\t\t...(replayed > 0 ? { queued: true } : {}),\n\t\t\t}),\n\t\t);\n\t\treturn replayed;\n\t};\n\n\treturn {\n\t\tget contextRevision() {\n\t\t\treturn contextRevision;\n\t\t},\n\t\tcommitMemory({ atom, operationId }) {\n\t\t\tassertNotRevoked();\n\t\t\tassertNonEmptyString(operationId, \"operationId\");\n\t\t\tif (state === \"compacting\") {\n\t\t\t\tconst validated = validateMemoryAtomV1(atom);\n\t\t\t\tpending.push({ atom: validated, operationId });\n\t\t\t\tjournalLog.push(\n\t\t\t\t\tObject.freeze({\n\t\t\t\t\t\tkind: \"memory_commit\",\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\tcontextRevision,\n\t\t\t\t\t\tmemoryId: validated.memoryId,\n\t\t\t\t\t\tqueued: true,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn Object.freeze({\n\t\t\t\t\tcommitted: undefined as never,\n\t\t\t\t\tcontextRevision,\n\t\t\t\t\toperationId,\n\t\t\t\t\tprojectionPending: true,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn commitNow(atom, operationId);\n\t\t},\n\t\tbeginCompaction({ operationId, expectedContextRevision }) {\n\t\t\tassertNotRevoked();\n\t\t\tassertNonEmptyString(operationId, \"compaction operationId\");\n\t\t\tif (state === \"compacting\") {\n\t\t\t\tthrow new Error(`A compaction is already running: ${activeCompactionOperationId}`);\n\t\t\t}\n\t\t\tif (expectedContextRevision !== contextRevision) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Context revision mismatch: expected ${expectedContextRevision}, current ${contextRevision} (stale compaction input)`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tstate = \"compacting\";\n\t\t\tactiveCompactionOperationId = operationId;\n\t\t\tjournalLog.push(Object.freeze({ kind: \"compaction_started\", operationId, contextRevision }));\n\t\t},\n\t\tcompleteCompaction({ operationId, newContextRevision }) {\n\t\t\tassertNotRevoked();\n\t\t\tif (state !== \"compacting\" || activeCompactionOperationId !== operationId) {\n\t\t\t\tthrow new Error(`No running compaction with operationId ${operationId}`);\n\t\t\t}\n\t\t\tcontextRevision = newContextRevision ?? contextRevision + 1;\n\t\t\tstate = \"idle\";\n\t\t\tconst replayed = replayPending(operationId);\n\t\t\tactiveCompactionOperationId = undefined;\n\t\t\treturn replayed;\n\t\t},\n\t\tfailCompaction({ operationId, reason }) {\n\t\t\tassertNotRevoked();\n\t\t\tif (state !== \"compacting\" || activeCompactionOperationId !== operationId) {\n\t\t\t\tthrow new Error(`No running compaction with operationId ${operationId}`);\n\t\t\t}\n\t\t\tstate = \"idle\";\n\t\t\t// Abort failure mode (D-027): the compaction failed, but memories that\n\t\t\t// were committed or queued during it are replayed in order — a failed\n\t\t\t// compaction never deletes committed facts.\n\t\t\tconst replayed = replayPending(operationId, reason);\n\t\t\tactiveCompactionOperationId = undefined;\n\t\t\treturn replayed;\n\t\t},\n\t\tsetRevoked(reason) {\n\t\t\tstate = \"revoked\";\n\t\t\trevokedReason = reason;\n\t\t},\n\t\tpendingCommits() {\n\t\t\treturn pending.length;\n\t\t},\n\t\tjournal() {\n\t\t\treturn Object.freeze([...journalLog]);\n\t\t},\n\t};\n}\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n"]}
@@ -0,0 +1,129 @@
1
+ import { validateMemoryAtomV1 } from "./foundation.js";
2
+ export function createMemoryCompactionSequencer(options) {
3
+ const store = options.store;
4
+ let state = "idle";
5
+ let contextRevision = 1;
6
+ let revokedReason;
7
+ let activeCompactionOperationId;
8
+ const pending = [];
9
+ const journalLog = [];
10
+ const assertNotRevoked = () => {
11
+ if (state === "revoked") {
12
+ // v8 ignore next -- setRevoked 必带 reason
13
+ throw new Error(`Memory sequencer is revoked: ${revokedReason ?? "revoked"}; late writes must fail`);
14
+ }
15
+ };
16
+ const commitNow = (atom, operationId, flags = {}) => {
17
+ const validated = validateMemoryAtomV1(atom);
18
+ const committed = store.commit(validated);
19
+ const fact = Object.freeze({
20
+ kind: "memory_commit",
21
+ operationId,
22
+ contextRevision,
23
+ memoryId: committed.atom.memoryId,
24
+ ...(flags.replayed === undefined ? {} : { replayed: true }),
25
+ });
26
+ journalLog.push(fact);
27
+ return Object.freeze({
28
+ committed,
29
+ contextRevision,
30
+ operationId,
31
+ ...(flags.replayed === undefined ? {} : { replayed: true }),
32
+ });
33
+ };
34
+ const replayPending = (compactionOperationId, failureReason) => {
35
+ let replayed = 0;
36
+ while (pending.length > 0) {
37
+ const entry = pending.shift();
38
+ commitNow(entry.atom, entry.operationId, { replayed: true });
39
+ replayed += 1;
40
+ }
41
+ journalLog.push(Object.freeze({
42
+ kind: failureReason === undefined ? "compaction_completed" : "compaction_failed",
43
+ operationId: compactionOperationId,
44
+ contextRevision,
45
+ ...(failureReason === undefined ? {} : { reason: failureReason }),
46
+ ...(replayed > 0 ? { queued: true } : {}),
47
+ }));
48
+ return replayed;
49
+ };
50
+ return {
51
+ get contextRevision() {
52
+ return contextRevision;
53
+ },
54
+ commitMemory({ atom, operationId }) {
55
+ assertNotRevoked();
56
+ assertNonEmptyString(operationId, "operationId");
57
+ if (state === "compacting") {
58
+ const validated = validateMemoryAtomV1(atom);
59
+ pending.push({ atom: validated, operationId });
60
+ journalLog.push(Object.freeze({
61
+ kind: "memory_commit",
62
+ operationId,
63
+ contextRevision,
64
+ memoryId: validated.memoryId,
65
+ queued: true,
66
+ }));
67
+ return Object.freeze({
68
+ committed: undefined,
69
+ contextRevision,
70
+ operationId,
71
+ projectionPending: true,
72
+ });
73
+ }
74
+ return commitNow(atom, operationId);
75
+ },
76
+ beginCompaction({ operationId, expectedContextRevision }) {
77
+ assertNotRevoked();
78
+ assertNonEmptyString(operationId, "compaction operationId");
79
+ if (state === "compacting") {
80
+ throw new Error(`A compaction is already running: ${activeCompactionOperationId}`);
81
+ }
82
+ if (expectedContextRevision !== contextRevision) {
83
+ throw new Error(`Context revision mismatch: expected ${expectedContextRevision}, current ${contextRevision} (stale compaction input)`);
84
+ }
85
+ state = "compacting";
86
+ activeCompactionOperationId = operationId;
87
+ journalLog.push(Object.freeze({ kind: "compaction_started", operationId, contextRevision }));
88
+ },
89
+ completeCompaction({ operationId, newContextRevision }) {
90
+ assertNotRevoked();
91
+ if (state !== "compacting" || activeCompactionOperationId !== operationId) {
92
+ throw new Error(`No running compaction with operationId ${operationId}`);
93
+ }
94
+ contextRevision = newContextRevision ?? contextRevision + 1;
95
+ state = "idle";
96
+ const replayed = replayPending(operationId);
97
+ activeCompactionOperationId = undefined;
98
+ return replayed;
99
+ },
100
+ failCompaction({ operationId, reason }) {
101
+ assertNotRevoked();
102
+ if (state !== "compacting" || activeCompactionOperationId !== operationId) {
103
+ throw new Error(`No running compaction with operationId ${operationId}`);
104
+ }
105
+ state = "idle";
106
+ // Abort failure mode (D-027): the compaction failed, but memories that
107
+ // were committed or queued during it are replayed in order — a failed
108
+ // compaction never deletes committed facts.
109
+ const replayed = replayPending(operationId, reason);
110
+ activeCompactionOperationId = undefined;
111
+ return replayed;
112
+ },
113
+ setRevoked(reason) {
114
+ state = "revoked";
115
+ revokedReason = reason;
116
+ },
117
+ pendingCommits() {
118
+ return pending.length;
119
+ },
120
+ journal() {
121
+ return Object.freeze([...journalLog]);
122
+ },
123
+ };
124
+ }
125
+ function assertNonEmptyString(value, label) {
126
+ if (typeof value !== "string" || value.trim().length === 0)
127
+ throw new Error(`${label} must be a non-empty string`);
128
+ }
129
+ //# sourceMappingURL=compaction-sequencer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction-sequencer.js","sourceRoot":"","sources":["../../src/memory/compaction-sequencer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — memory commit ordering across compaction (1C.7a).\n *\n * Adapts memory commits to the 1B.3c revision-checked session/context commit:\n * - Memory is committed BEFORE compaction; a failed compaction can never\n * delete already-committed memories.\n * - Commits that arrive DURING a compaction are queued (projection-pending)\n * and replayed in order when the compaction settles — success or failure.\n * - The compaction commit and every memory commit carry an operationId and\n * the context revision they are causally bound to; the journal makes that\n * ordering observable.\n * - After revoke/reload/dispose, late memory writes fail explicitly.\n * - `abort` failure mode (D-027): the compaction failure is recorded and the\n * queued memories replay — degraded-visible marking belongs to the Profile\n * layer (2.x), not to this sequencer.\n */\nimport type { MemoryAtomV1 } from \"./foundation.ts\";\nimport { validateMemoryAtomV1 } from \"./foundation.ts\";\nimport type { CommittedMemoryV1, MemoryStoreV1 } from \"./store.ts\";\n\nexport type MemorySequencerStateV1 = \"idle\" | \"compacting\" | \"revoked\";\n\nexport interface MemorySequencerJournalFactV1 {\n\treadonly kind: \"memory_commit\" | \"compaction_started\" | \"compaction_completed\" | \"compaction_failed\";\n\treadonly operationId: string;\n\treadonly contextRevision: number;\n\treadonly memoryId?: string;\n\treadonly reason?: string;\n\treadonly queued?: boolean;\n\treadonly replayed?: boolean;\n}\n\nexport interface MemorySequencedCommitV1 {\n\treadonly committed: CommittedMemoryV1;\n\treadonly contextRevision: number;\n\treadonly operationId: string;\n\t/** True when the commit was queued during a compaction and replayed after. */\n\treadonly replayed?: boolean;\n\t/** True when the commit is held until the running compaction settles. */\n\treadonly projectionPending?: boolean;\n}\n\nexport interface MemoryCompactionSequencerV1 {\n\tcommitMemory(input: { readonly atom: MemoryAtomV1; readonly operationId: string }): MemorySequencedCommitV1;\n\tbeginCompaction(input: { readonly operationId: string; readonly expectedContextRevision: number }): void;\n\tcompleteCompaction(input: { readonly operationId: string; readonly newContextRevision?: number }): number;\n\tfailCompaction(input: { readonly operationId: string; readonly reason: string }): number;\n\tsetRevoked(reason: string): void;\n\treadonly contextRevision: number;\n\tpendingCommits(): number;\n\tjournal(): readonly MemorySequencerJournalFactV1[];\n}\n\nexport function createMemoryCompactionSequencer(options: {\n\treadonly store: MemoryStoreV1;\n}): MemoryCompactionSequencerV1 {\n\tconst store = options.store;\n\tlet state: MemorySequencerStateV1 = \"idle\";\n\tlet contextRevision = 1;\n\tlet revokedReason: string | undefined;\n\tlet activeCompactionOperationId: string | undefined;\n\tconst pending: Array<{ atom: MemoryAtomV1; operationId: string }> = [];\n\tconst journalLog: MemorySequencerJournalFactV1[] = [];\n\n\tconst assertNotRevoked = (): void => {\n\t\tif (state === \"revoked\") {\n\t\t\t// v8 ignore next -- setRevoked 必带 reason\n\t\t\tthrow new Error(`Memory sequencer is revoked: ${revokedReason ?? \"revoked\"}; late writes must fail`);\n\t\t}\n\t};\n\n\tconst commitNow = (\n\t\tatom: MemoryAtomV1,\n\t\toperationId: string,\n\t\tflags: { replayed?: boolean } = {},\n\t): MemorySequencedCommitV1 => {\n\t\tconst validated = validateMemoryAtomV1(atom);\n\t\tconst committed = store.commit(validated);\n\t\tconst fact: MemorySequencerJournalFactV1 = Object.freeze({\n\t\t\tkind: \"memory_commit\",\n\t\t\toperationId,\n\t\t\tcontextRevision,\n\t\t\tmemoryId: committed.atom.memoryId,\n\t\t\t...(flags.replayed === undefined ? {} : { replayed: true }),\n\t\t});\n\t\tjournalLog.push(fact);\n\t\treturn Object.freeze({\n\t\t\tcommitted,\n\t\t\tcontextRevision,\n\t\t\toperationId,\n\t\t\t...(flags.replayed === undefined ? {} : { replayed: true }),\n\t\t});\n\t};\n\n\tconst replayPending = (compactionOperationId: string, failureReason?: string): number => {\n\t\tlet replayed = 0;\n\t\twhile (pending.length > 0) {\n\t\t\tconst entry = pending.shift()!;\n\t\t\tcommitNow(entry.atom, entry.operationId, { replayed: true });\n\t\t\treplayed += 1;\n\t\t}\n\t\tjournalLog.push(\n\t\t\tObject.freeze({\n\t\t\t\tkind: failureReason === undefined ? \"compaction_completed\" : \"compaction_failed\",\n\t\t\t\toperationId: compactionOperationId,\n\t\t\t\tcontextRevision,\n\t\t\t\t...(failureReason === undefined ? {} : { reason: failureReason }),\n\t\t\t\t...(replayed > 0 ? { queued: true } : {}),\n\t\t\t}),\n\t\t);\n\t\treturn replayed;\n\t};\n\n\treturn {\n\t\tget contextRevision() {\n\t\t\treturn contextRevision;\n\t\t},\n\t\tcommitMemory({ atom, operationId }) {\n\t\t\tassertNotRevoked();\n\t\t\tassertNonEmptyString(operationId, \"operationId\");\n\t\t\tif (state === \"compacting\") {\n\t\t\t\tconst validated = validateMemoryAtomV1(atom);\n\t\t\t\tpending.push({ atom: validated, operationId });\n\t\t\t\tjournalLog.push(\n\t\t\t\t\tObject.freeze({\n\t\t\t\t\t\tkind: \"memory_commit\",\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\tcontextRevision,\n\t\t\t\t\t\tmemoryId: validated.memoryId,\n\t\t\t\t\t\tqueued: true,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn Object.freeze({\n\t\t\t\t\tcommitted: undefined as never,\n\t\t\t\t\tcontextRevision,\n\t\t\t\t\toperationId,\n\t\t\t\t\tprojectionPending: true,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn commitNow(atom, operationId);\n\t\t},\n\t\tbeginCompaction({ operationId, expectedContextRevision }) {\n\t\t\tassertNotRevoked();\n\t\t\tassertNonEmptyString(operationId, \"compaction operationId\");\n\t\t\tif (state === \"compacting\") {\n\t\t\t\tthrow new Error(`A compaction is already running: ${activeCompactionOperationId}`);\n\t\t\t}\n\t\t\tif (expectedContextRevision !== contextRevision) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Context revision mismatch: expected ${expectedContextRevision}, current ${contextRevision} (stale compaction input)`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tstate = \"compacting\";\n\t\t\tactiveCompactionOperationId = operationId;\n\t\t\tjournalLog.push(Object.freeze({ kind: \"compaction_started\", operationId, contextRevision }));\n\t\t},\n\t\tcompleteCompaction({ operationId, newContextRevision }) {\n\t\t\tassertNotRevoked();\n\t\t\tif (state !== \"compacting\" || activeCompactionOperationId !== operationId) {\n\t\t\t\tthrow new Error(`No running compaction with operationId ${operationId}`);\n\t\t\t}\n\t\t\tcontextRevision = newContextRevision ?? contextRevision + 1;\n\t\t\tstate = \"idle\";\n\t\t\tconst replayed = replayPending(operationId);\n\t\t\tactiveCompactionOperationId = undefined;\n\t\t\treturn replayed;\n\t\t},\n\t\tfailCompaction({ operationId, reason }) {\n\t\t\tassertNotRevoked();\n\t\t\tif (state !== \"compacting\" || activeCompactionOperationId !== operationId) {\n\t\t\t\tthrow new Error(`No running compaction with operationId ${operationId}`);\n\t\t\t}\n\t\t\tstate = \"idle\";\n\t\t\t// Abort failure mode (D-027): the compaction failed, but memories that\n\t\t\t// were committed or queued during it are replayed in order — a failed\n\t\t\t// compaction never deletes committed facts.\n\t\t\tconst replayed = replayPending(operationId, reason);\n\t\t\tactiveCompactionOperationId = undefined;\n\t\t\treturn replayed;\n\t\t},\n\t\tsetRevoked(reason) {\n\t\t\tstate = \"revoked\";\n\t\t\trevokedReason = reason;\n\t\t},\n\t\tpendingCommits() {\n\t\t\treturn pending.length;\n\t\t},\n\t\tjournal() {\n\t\t\treturn Object.freeze([...journalLog]);\n\t\t},\n\t};\n}\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n"]}