@remnic/core 9.7.9 → 9.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.js +5 -5
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +6 -6
- package/dist/access-cli.js +36 -36
- package/dist/access-http.d.ts +2 -1
- package/dist/access-http.js +10 -10
- package/dist/access-identity-continuity-surface.js +5 -5
- package/dist/access-lcm-surface.js +5 -5
- package/dist/access-mcp.js +9 -9
- package/dist/access-observe-write-surface.js +5 -5
- package/dist/access-operations-batch.js +7 -7
- package/dist/access-operations.d.ts +4 -4
- package/dist/access-operations.js +8 -8
- package/dist/access-recall-surface.js +5 -5
- package/dist/access-schema.d.ts +64 -64
- package/dist/access-service.js +5 -5
- package/dist/access-surface-catalog.js +2 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/briefing.js +3 -3
- package/dist/causal-consolidation.js +4 -4
- package/dist/{chunk-HHAA35A3.js → chunk-2VN4JAX7.js} +548 -161
- package/dist/chunk-2VN4JAX7.js.map +1 -0
- package/dist/{chunk-MLT5EG6K.js → chunk-3OQ4IZJN.js} +4 -4
- package/dist/{chunk-DLPXS7BE.js → chunk-5U65RIVI.js} +348 -49
- package/dist/chunk-5U65RIVI.js.map +1 -0
- package/dist/{chunk-QKVX4Y6D.js → chunk-673NPXE3.js} +2 -2
- package/dist/{chunk-POUD5BLV.js → chunk-6LM6PPDX.js} +4 -2
- package/dist/chunk-6LM6PPDX.js.map +1 -0
- package/dist/chunk-7XRUYC6G.js +1794 -0
- package/dist/chunk-7XRUYC6G.js.map +1 -0
- package/dist/{chunk-5CFHYQU3.js → chunk-DLWYAN6A.js} +2 -2
- package/dist/{chunk-TY43GBX2.js → chunk-EQF5763Z.js} +2 -2
- package/dist/{chunk-SKXBOZDF.js → chunk-GHVJASQ4.js} +3 -3
- package/dist/{chunk-YGAOLHKN.js → chunk-JYRFO4EH.js} +4 -4
- package/dist/{chunk-F3FC7SMO.js → chunk-JZM5QUTE.js} +2 -2
- package/dist/{chunk-PV7E2FGY.js → chunk-KXLRMM35.js} +6 -6
- package/dist/{chunk-PHXCPVSX.js → chunk-L4H6MQ7I.js} +2 -2
- package/dist/{chunk-FKCJH7KP.js → chunk-LQKITFGB.js} +2 -2
- package/dist/{chunk-27VEKQQA.js → chunk-MXRMNKSY.js} +3 -3
- package/dist/{chunk-EIKBKQ6L.js → chunk-QHT4W3RU.js} +127 -9
- package/dist/chunk-QHT4W3RU.js.map +1 -0
- package/dist/{chunk-CDGGQGUU.js → chunk-SBFDL2EQ.js} +2 -2
- package/dist/{chunk-I2THZUAF.js → chunk-SECMZACR.js} +2 -2
- package/dist/{chunk-JU3GHO6J.js → chunk-TGBJ2OMK.js} +2 -2
- package/dist/{chunk-DE6ENYLG.js → chunk-TXLGHDHS.js} +2 -2
- package/dist/{chunk-KGNFFCZJ.js → chunk-U4KHQTJ6.js} +2 -2
- package/dist/{chunk-Y5U35ZVS.js → chunk-UTDA43TB.js} +2 -2
- package/dist/{chunk-CK6RI52P.js → chunk-VCEH5UYS.js} +2 -2
- package/dist/{chunk-UXWGPODH.js → chunk-VRAD2FGO.js} +7 -7
- package/dist/{chunk-KKASNHGF.js → chunk-W6R43NLA.js} +19 -19
- package/dist/{chunk-UP25ZZCN.js → chunk-WUDBMLC6.js} +2 -2
- package/dist/{chunk-XG5CGR3K.js → chunk-XF4KOVDK.js} +5 -5
- package/dist/{chunk-KPXAJ77O.js → chunk-YLTG6BO4.js} +2 -2
- package/dist/cli.js +23 -23
- package/dist/compounding/engine.js +3 -3
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/index.js +3 -3
- package/dist/entity-retrieval.js +3 -3
- package/dist/index.d.ts +3990 -4
- package/dist/index.js +90 -43
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +8 -8
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/namespaces/migrate.js +4 -4
- package/dist/namespaces/storage.js +3 -3
- package/dist/operator-toolkit.js +7 -7
- package/dist/orchestration/maintenance.js +5 -5
- package/dist/orchestrator.js +36 -36
- package/dist/retrieval-agents.js +2 -2
- package/dist/schemas.d.ts +74 -74
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/storage.js +2 -2
- package/dist/temporal-index-compat.d.ts +1 -0
- package/dist/temporal-index-compat.js +16 -0
- package/dist/temporal-index-compat.js.map +1 -0
- package/dist/temporal-index.d.ts +58 -7
- package/dist/temporal-index.js +23 -3
- package/dist/transfer/types.d.ts +66 -66
- package/dist/verified-recall.js +3 -3
- package/package.json +2 -2
- package/src/access-boundary.ts +2 -0
- package/src/access-http.ts +147 -6
- package/src/access-mcp-cancellation.test.ts +2 -2
- package/src/access-operations.ts +2 -0
- package/src/access-surface-catalog.ts +2 -0
- package/src/index.ts +53 -0
- package/src/orchestration/consolidation-run.ts +5 -5
- package/src/orchestration/contradiction-linking-coordinator.ts +2 -2
- package/src/orchestration/lifecycle-policy-coordinator.ts +2 -2
- package/src/orchestration/persistence-index.ts +6 -6
- package/src/orchestration/recall-search-pipeline.ts +36 -21
- package/src/orchestration/semantic-consolidation-coordinator.ts +2 -2
- package/src/orchestrator.ts +0 -4
- package/src/relay/mission-access.test.ts +379 -0
- package/src/relay/mission-access.ts +125 -0
- package/src/relay/mission-fixture.ts +301 -0
- package/src/relay/mission.test.ts +964 -0
- package/src/relay/mission.ts +1725 -0
- package/src/temporal-index-compat.ts +311 -0
- package/src/temporal-index.test.ts +273 -22
- package/src/temporal-index.ts +427 -178
- package/dist/chunk-DLPXS7BE.js.map +0 -1
- package/dist/chunk-EIKBKQ6L.js.map +0 -1
- package/dist/chunk-HHAA35A3.js.map +0 -1
- package/dist/chunk-POUD5BLV.js.map +0 -1
- package/dist/chunk-VCFADTFS.js +0 -467
- package/dist/chunk-VCFADTFS.js.map +0 -1
- /package/dist/{chunk-MLT5EG6K.js.map → chunk-3OQ4IZJN.js.map} +0 -0
- /package/dist/{chunk-QKVX4Y6D.js.map → chunk-673NPXE3.js.map} +0 -0
- /package/dist/{chunk-5CFHYQU3.js.map → chunk-DLWYAN6A.js.map} +0 -0
- /package/dist/{chunk-TY43GBX2.js.map → chunk-EQF5763Z.js.map} +0 -0
- /package/dist/{chunk-SKXBOZDF.js.map → chunk-GHVJASQ4.js.map} +0 -0
- /package/dist/{chunk-YGAOLHKN.js.map → chunk-JYRFO4EH.js.map} +0 -0
- /package/dist/{chunk-F3FC7SMO.js.map → chunk-JZM5QUTE.js.map} +0 -0
- /package/dist/{chunk-PV7E2FGY.js.map → chunk-KXLRMM35.js.map} +0 -0
- /package/dist/{chunk-PHXCPVSX.js.map → chunk-L4H6MQ7I.js.map} +0 -0
- /package/dist/{chunk-FKCJH7KP.js.map → chunk-LQKITFGB.js.map} +0 -0
- /package/dist/{chunk-27VEKQQA.js.map → chunk-MXRMNKSY.js.map} +0 -0
- /package/dist/{chunk-CDGGQGUU.js.map → chunk-SBFDL2EQ.js.map} +0 -0
- /package/dist/{chunk-I2THZUAF.js.map → chunk-SECMZACR.js.map} +0 -0
- /package/dist/{chunk-JU3GHO6J.js.map → chunk-TGBJ2OMK.js.map} +0 -0
- /package/dist/{chunk-DE6ENYLG.js.map → chunk-TXLGHDHS.js.map} +0 -0
- /package/dist/{chunk-KGNFFCZJ.js.map → chunk-U4KHQTJ6.js.map} +0 -0
- /package/dist/{chunk-Y5U35ZVS.js.map → chunk-UTDA43TB.js.map} +0 -0
- /package/dist/{chunk-CK6RI52P.js.map → chunk-VCEH5UYS.js.map} +0 -0
- /package/dist/{chunk-UXWGPODH.js.map → chunk-VRAD2FGO.js.map} +0 -0
- /package/dist/{chunk-KKASNHGF.js.map → chunk-W6R43NLA.js.map} +0 -0
- /package/dist/{chunk-UP25ZZCN.js.map → chunk-WUDBMLC6.js.map} +0 -0
- /package/dist/{chunk-XG5CGR3K.js.map → chunk-XF4KOVDK.js.map} +0 -0
- /package/dist/{chunk-KPXAJ77O.js.map → chunk-YLTG6BO4.js.map} +0 -0
package/src/access-http.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { handleChatMessage, handleChatEventsSSE } from "./chat/chat-http.js";
|
|
|
53
53
|
import { cleanupExpiredChatSessions, enforceChatSessionNamespace } from "./chat/chat-session.js";
|
|
54
54
|
import { isDefaultReviewNamespace, listPairs, readPair } from "./contradiction/contradiction-review.js";
|
|
55
55
|
import { isValidResolutionVerb, executeResolution } from "./contradiction/resolution.js";
|
|
56
|
+
import { RelayMissionStoreError } from "./relay/mission.js";
|
|
56
57
|
|
|
57
58
|
export interface AccessHttpReadinessState {
|
|
58
59
|
ready: boolean;
|
|
@@ -339,6 +340,22 @@ function decodePeerIdSegment(raw: string): string {
|
|
|
339
340
|
}
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
function decodeRelayMissionIdSegment(raw: string): string {
|
|
344
|
+
try {
|
|
345
|
+
return decodeURIComponent(raw);
|
|
346
|
+
} catch {
|
|
347
|
+
throw new EngramAccessInputError(
|
|
348
|
+
"missionId path segment is not valid percent-encoded input",
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function rejectBlankRelayNamespace(value: string | null | undefined): void {
|
|
354
|
+
if (value !== undefined && value !== null && value.trim().length === 0) {
|
|
355
|
+
throw new EngramAccessInputError("Relay namespace must not be blank when provided");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
342
359
|
function codingContextFromProjectTag(projectTag: string): {
|
|
343
360
|
projectId: string;
|
|
344
361
|
branch: string | null;
|
|
@@ -378,7 +395,7 @@ export class EngramAccessHttpServer {
|
|
|
378
395
|
res: ServerResponse,
|
|
379
396
|
ctx: { authorized: boolean },
|
|
380
397
|
) => Promise<boolean>;
|
|
381
|
-
private readonly
|
|
398
|
+
private readonly writeRequestSlots: Array<{ readonly recordedAt: number }> = [];
|
|
382
399
|
private readonly writeRateLimitMaxRequests: number;
|
|
383
400
|
private readonly writeRateLimitWindowMs: number;
|
|
384
401
|
private readonly mcpServer: EngramMcpServer;
|
|
@@ -499,6 +516,20 @@ export class EngramAccessHttpServer {
|
|
|
499
516
|
this.respondJson(res, 400, { error: err.message, code: "correction_contract_error" });
|
|
500
517
|
return;
|
|
501
518
|
}
|
|
519
|
+
if (err instanceof RelayMissionStoreError) {
|
|
520
|
+
const status = err.code === "idempotency_conflict"
|
|
521
|
+
? 409
|
|
522
|
+
: err.code === "limit_exceeded"
|
|
523
|
+
? 413
|
|
524
|
+
: err.code === "lock_unavailable"
|
|
525
|
+
? 503
|
|
526
|
+
: 500;
|
|
527
|
+
this.respondJson(res, status, {
|
|
528
|
+
error: status >= 500 ? "relay_backend_unavailable" : err.message,
|
|
529
|
+
code: `relay_${err.code}`,
|
|
530
|
+
});
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
502
533
|
if (res.headersSent) {
|
|
503
534
|
res.destroy(err as Error);
|
|
504
535
|
return;
|
|
@@ -1670,6 +1701,103 @@ export class EngramAccessHttpServer {
|
|
|
1670
1701
|
return;
|
|
1671
1702
|
}
|
|
1672
1703
|
|
|
1704
|
+
// Remnic Relay mission receipts (issue #1966). These two routes share the
|
|
1705
|
+
// access-boundary schemas and namespace-resolved storage contract. The
|
|
1706
|
+
// browser receives an already-reduced snapshot; it never scans general
|
|
1707
|
+
// memories, recall logs, or production state.
|
|
1708
|
+
const relayEventMatch = /^\/engram\/v1\/relay\/missions\/([^/]+)\/events$/.exec(pathname);
|
|
1709
|
+
if (req.method === "POST" && relayEventMatch) {
|
|
1710
|
+
const missionId = decodeRelayMissionIdSegment(relayEventMatch[1] ?? "");
|
|
1711
|
+
const body = await this.readJsonBody(req);
|
|
1712
|
+
const unexpectedFields = Object.keys(body).filter(
|
|
1713
|
+
(key) => key !== "namespace" && key !== "event",
|
|
1714
|
+
);
|
|
1715
|
+
if (unexpectedFields.length > 0) {
|
|
1716
|
+
throw new EngramAccessInputError(
|
|
1717
|
+
`Relay append body contains unexpected field(s): ${unexpectedFields.sort().join(", ")}`,
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
if (!Object.prototype.hasOwnProperty.call(body, "event")) {
|
|
1721
|
+
throw new EngramAccessInputError("Relay append body must contain an event object");
|
|
1722
|
+
}
|
|
1723
|
+
if (
|
|
1724
|
+
Object.prototype.hasOwnProperty.call(body, "namespace") &&
|
|
1725
|
+
body.namespace !== undefined &&
|
|
1726
|
+
body.namespace !== null &&
|
|
1727
|
+
typeof body.namespace !== "string"
|
|
1728
|
+
) {
|
|
1729
|
+
throw new EngramAccessInputError("namespace must be a string when provided");
|
|
1730
|
+
}
|
|
1731
|
+
rejectBlankRelayNamespace(
|
|
1732
|
+
typeof body.namespace === "string" ? body.namespace : undefined,
|
|
1733
|
+
);
|
|
1734
|
+
const namespace = this.resolveNamespace(
|
|
1735
|
+
req,
|
|
1736
|
+
typeof body.namespace === "string" ? body.namespace : undefined,
|
|
1737
|
+
);
|
|
1738
|
+
const op = getOperation("relay_mission_append");
|
|
1739
|
+
if (!op) {
|
|
1740
|
+
throw new Error("access-boundary: operation not registered: relay_mission_append");
|
|
1741
|
+
}
|
|
1742
|
+
let releaseWriteQuota: (() => void) | undefined;
|
|
1743
|
+
try {
|
|
1744
|
+
const output = (await op.run(
|
|
1745
|
+
{ missionId, namespace, event: body.event },
|
|
1746
|
+
{
|
|
1747
|
+
service: this.service,
|
|
1748
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
1749
|
+
hooks: {
|
|
1750
|
+
enforceWriteQuota: () => {
|
|
1751
|
+
releaseWriteQuota ??= this.reserveWriteRateLimitSlot();
|
|
1752
|
+
},
|
|
1753
|
+
},
|
|
1754
|
+
},
|
|
1755
|
+
)) as { result: { appended: boolean; replayed: boolean; event: unknown } };
|
|
1756
|
+
if (output.result.appended) {
|
|
1757
|
+
// The reservation is now the committed quota hit. Do not record a
|
|
1758
|
+
// second timestamp after the append returns.
|
|
1759
|
+
releaseWriteQuota = undefined;
|
|
1760
|
+
}
|
|
1761
|
+
this.respondJson(res, output.result.appended ? 201 : 200, output.result);
|
|
1762
|
+
} finally {
|
|
1763
|
+
// Replays never invoke the hook. If a future store path invokes it but
|
|
1764
|
+
// does not append, or persistence throws after reservation, return the
|
|
1765
|
+
// slot so a failed write cannot exhaust the rolling window.
|
|
1766
|
+
releaseWriteQuota?.();
|
|
1767
|
+
}
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
const relayMissionMatch = /^\/engram\/v1\/relay\/missions\/([^/]+)$/.exec(pathname);
|
|
1772
|
+
if (req.method === "GET" && relayMissionMatch) {
|
|
1773
|
+
const missionId = decodeRelayMissionIdSegment(relayMissionMatch[1] ?? "");
|
|
1774
|
+
rejectBlankRelayNamespace(parsed.searchParams.get("namespace"));
|
|
1775
|
+
const namespace = this.resolveNamespace(
|
|
1776
|
+
req,
|
|
1777
|
+
parsed.searchParams.get("namespace") ?? undefined,
|
|
1778
|
+
);
|
|
1779
|
+
const op = getOperation("relay_mission_read");
|
|
1780
|
+
if (!op) {
|
|
1781
|
+
throw new Error("access-boundary: operation not registered: relay_mission_read");
|
|
1782
|
+
}
|
|
1783
|
+
const output = (await op.run(
|
|
1784
|
+
{
|
|
1785
|
+
missionId,
|
|
1786
|
+
namespace,
|
|
1787
|
+
since: parsed.searchParams.get("since") ?? undefined,
|
|
1788
|
+
until: parsed.searchParams.get("until") ?? undefined,
|
|
1789
|
+
limit: parsed.searchParams.get("limit") ?? undefined,
|
|
1790
|
+
},
|
|
1791
|
+
{
|
|
1792
|
+
service: this.service,
|
|
1793
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
1794
|
+
},
|
|
1795
|
+
)) as { result: unknown };
|
|
1796
|
+
res.setHeader("cache-control", "no-store");
|
|
1797
|
+
this.respondJson(res, 200, output.result);
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1673
1801
|
if (req.method === "POST" && pathname === "/engram/v1/memories") {
|
|
1674
1802
|
// Migrated through the access boundary (issue #1525): the registry
|
|
1675
1803
|
// entry owns schema validation, normalization, and service dispatch.
|
|
@@ -3711,18 +3839,31 @@ export class EngramAccessHttpServer {
|
|
|
3711
3839
|
private ensureWriteRateLimitAvailable(): void {
|
|
3712
3840
|
const now = Date.now();
|
|
3713
3841
|
while (
|
|
3714
|
-
this.
|
|
3715
|
-
now - (this.
|
|
3842
|
+
this.writeRequestSlots.length > 0 &&
|
|
3843
|
+
now - (this.writeRequestSlots[0]?.recordedAt ?? 0) > this.writeRateLimitWindowMs
|
|
3716
3844
|
) {
|
|
3717
|
-
this.
|
|
3845
|
+
this.writeRequestSlots.shift();
|
|
3718
3846
|
}
|
|
3719
|
-
if (this.
|
|
3847
|
+
if (this.writeRequestSlots.length >= this.writeRateLimitMaxRequests) {
|
|
3720
3848
|
throw new HttpError(429, "write_rate_limited", "write_rate_limited");
|
|
3721
3849
|
}
|
|
3722
3850
|
}
|
|
3723
3851
|
|
|
3724
3852
|
private recordWriteRateLimitHit(): void {
|
|
3725
|
-
this.
|
|
3853
|
+
this.writeRequestSlots.push({ recordedAt: Date.now() });
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
private reserveWriteRateLimitSlot(): () => void {
|
|
3857
|
+
this.ensureWriteRateLimitAvailable();
|
|
3858
|
+
const slot = { recordedAt: Date.now() };
|
|
3859
|
+
this.writeRequestSlots.push(slot);
|
|
3860
|
+
let active = true;
|
|
3861
|
+
return () => {
|
|
3862
|
+
if (!active) return;
|
|
3863
|
+
active = false;
|
|
3864
|
+
const index = this.writeRequestSlots.indexOf(slot);
|
|
3865
|
+
if (index >= 0) this.writeRequestSlots.splice(index, 1);
|
|
3866
|
+
};
|
|
3726
3867
|
}
|
|
3727
3868
|
|
|
3728
3869
|
private shouldCountWriteRateLimit(response: { dryRun?: boolean; idempotencyReplay?: boolean }): boolean {
|
|
@@ -282,7 +282,7 @@ test("MCP write quota is recorded after commit even when the client disconnects
|
|
|
282
282
|
});
|
|
283
283
|
const serverHost = server as unknown as {
|
|
284
284
|
mcpServer: EngramMcpServer;
|
|
285
|
-
|
|
285
|
+
writeRequestSlots: Array<{ readonly recordedAt: number }>;
|
|
286
286
|
};
|
|
287
287
|
const originalHandleRequest = serverHost.mcpServer.handleRequest.bind(serverHost.mcpServer);
|
|
288
288
|
serverHost.mcpServer.handleRequest = async (request, options) => {
|
|
@@ -318,7 +318,7 @@ test("MCP write quota is recorded after commit even when the client disconnects
|
|
|
318
318
|
|
|
319
319
|
await waitFor(operationSettled.promise);
|
|
320
320
|
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
321
|
-
assert.equal(serverHost.
|
|
321
|
+
assert.equal(serverHost.writeRequestSlots.length, 1);
|
|
322
322
|
assert.equal(responseStarted, false, "the disconnected client must not receive the committed write response");
|
|
323
323
|
} finally {
|
|
324
324
|
releaseOperation.resolve();
|
package/src/access-operations.ts
CHANGED
|
@@ -678,6 +678,8 @@ export const codegraphIngestTracesOperation = defineOperation<
|
|
|
678
678
|
// Batch-migrated operations (issue #1525): registers all remaining
|
|
679
679
|
// handlers through the boundary as a side effect of importing this module.
|
|
680
680
|
import "./access-operations-batch.js";
|
|
681
|
+
// Remnic Relay mission evidence operations (issue #1966).
|
|
682
|
+
import "./relay/mission-access.js";
|
|
681
683
|
|
|
682
684
|
export const REGISTERED_OPERATIONS = [
|
|
683
685
|
memoryGetOperation.spec.name,
|
|
@@ -193,6 +193,8 @@ export const HTTP_ROUTES: readonly HttpRouteEntry[] = [
|
|
|
193
193
|
{ method: "POST", pathname: "/engram/v1/lcm/compaction/flush", operation: "lcm_compaction_flush" },
|
|
194
194
|
{ method: "POST", pathname: "/engram/v1/lcm/compaction/record", operation: "lcm_compaction_record" },
|
|
195
195
|
{ method: "GET", pathname: "/engram/v1/lcm/status", operation: "lcm_status" },
|
|
196
|
+
{ method: "GET", pathname: "/engram/v1/relay/missions/:id", operation: "relay_mission_read" },
|
|
197
|
+
{ method: "POST", pathname: "/engram/v1/relay/missions/:id/events", operation: "relay_mission_append" },
|
|
196
198
|
// Correction Contract (issue #1580) — plan/apply/pending.
|
|
197
199
|
{ method: "POST", pathname: "/engram/v1/correction/plan", operation: "memory_correct_plan" },
|
|
198
200
|
{ method: "POST", pathname: "/engram/v1/correction/apply", operation: "memory_correct_apply" },
|
package/src/index.ts
CHANGED
|
@@ -387,6 +387,59 @@ export {
|
|
|
387
387
|
} from "./access-http.js";
|
|
388
388
|
export { EngramMcpServer } from "./access-mcp.js";
|
|
389
389
|
|
|
390
|
+
// Remnic Relay — host-agnostic mission evidence and receipt contract.
|
|
391
|
+
export {
|
|
392
|
+
RELAY_MISSION_DEFAULT_EVENT_LIMIT,
|
|
393
|
+
RELAY_MISSION_MAX_EVENT_LIMIT,
|
|
394
|
+
RELAY_MISSION_MAX_EVENTS,
|
|
395
|
+
RELAY_MISSION_MAX_FILE_BYTES,
|
|
396
|
+
RELAY_MISSION_MAX_LINE_BYTES,
|
|
397
|
+
RELAY_MISSION_SCHEMA_VERSION,
|
|
398
|
+
RelayEvidenceRefSchema,
|
|
399
|
+
RelayMissionEventInputSchema,
|
|
400
|
+
RelayMissionEventSchema,
|
|
401
|
+
RelayMissionPayloadSchema,
|
|
402
|
+
RelayMissionReadOptionsSchema,
|
|
403
|
+
RelayMissionStore,
|
|
404
|
+
RelayMissionStoreError,
|
|
405
|
+
compareRelayEvents,
|
|
406
|
+
reduceRelayMission,
|
|
407
|
+
relayMissionReceiptDigest,
|
|
408
|
+
type RelayAgentOutputSnapshot,
|
|
409
|
+
type RelayAgentSnapshot,
|
|
410
|
+
type RelayConflictSnapshot,
|
|
411
|
+
type RelayCorrectionSnapshot,
|
|
412
|
+
type RelayDecisionSnapshot,
|
|
413
|
+
type RelayEvidenceRef,
|
|
414
|
+
type RelayMissionAppendResult,
|
|
415
|
+
type RelayMissionEvent,
|
|
416
|
+
type RelayMissionEventInput,
|
|
417
|
+
type RelayMissionPayload,
|
|
418
|
+
type RelayMissionReadOptions,
|
|
419
|
+
type RelayMissionSnapshot,
|
|
420
|
+
type RelayMissionStatus,
|
|
421
|
+
type RelayMissionStoreErrorCode,
|
|
422
|
+
type RelayMissionStoreOptions,
|
|
423
|
+
type RelayPropagationSnapshot,
|
|
424
|
+
type RelayRecallSnapshot,
|
|
425
|
+
type RelayTestSnapshot,
|
|
426
|
+
} from "./relay/mission.js";
|
|
427
|
+
export {
|
|
428
|
+
RELAY_DEMO_MISSION_ID,
|
|
429
|
+
RELAY_DEMO_NAMESPACE,
|
|
430
|
+
createRelayMissionFixture,
|
|
431
|
+
} from "./relay/mission-fixture.js";
|
|
432
|
+
export {
|
|
433
|
+
appendRelayMissionEvent,
|
|
434
|
+
readRelayMission,
|
|
435
|
+
relayMissionAppendOperation,
|
|
436
|
+
relayMissionReadOperation,
|
|
437
|
+
type RelayMissionAppendAccessInput,
|
|
438
|
+
type RelayMissionAppendAccessOutput,
|
|
439
|
+
type RelayMissionReadAccessInput,
|
|
440
|
+
type RelayMissionReadAccessOutput,
|
|
441
|
+
} from "./relay/mission-access.js";
|
|
442
|
+
|
|
390
443
|
// agentAccessHttp.authToken SecretRef resolution (issue #757). Exposed so
|
|
391
444
|
// host-specific bootstrap code (the OpenClaw plugin in `src/index.ts`, the
|
|
392
445
|
// standalone server, the CLI) can resolve a SecretRef to a literal bearer
|
|
@@ -30,7 +30,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
30
30
|
import { log } from "../logger.js";
|
|
31
31
|
import { applyCommitmentLedgerLifecycle } from "../commitment-ledger.js";
|
|
32
32
|
import { recordDreamsPhaseRun } from "../maintenance/dreams-ledger.js";
|
|
33
|
-
import {
|
|
33
|
+
import { deindexMemoryAsync } from "../temporal-index.js";
|
|
34
34
|
import { isActiveMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
|
|
35
35
|
import {
|
|
36
36
|
resolveConsolidationCapabilities,
|
|
@@ -165,7 +165,7 @@ export class ConsolidationRunCoordinator {
|
|
|
165
165
|
memoryItemMutated = true;
|
|
166
166
|
await this.deps.embeddingFallback.removeFromIndex(item.existingId);
|
|
167
167
|
if (toInvalidate?.path && toInvalidate.frontmatter?.created) {
|
|
168
|
-
|
|
168
|
+
await deindexMemoryAsync(
|
|
169
169
|
config.memoryDir,
|
|
170
170
|
toInvalidate.path,
|
|
171
171
|
toInvalidate.frontmatter.created,
|
|
@@ -216,7 +216,7 @@ export class ConsolidationRunCoordinator {
|
|
|
216
216
|
toMergeInvalidate?.path &&
|
|
217
217
|
toMergeInvalidate.frontmatter?.created
|
|
218
218
|
) {
|
|
219
|
-
|
|
219
|
+
await deindexMemoryAsync(
|
|
220
220
|
config.memoryDir,
|
|
221
221
|
toMergeInvalidate.path,
|
|
222
222
|
toMergeInvalidate.frontmatter.created,
|
|
@@ -293,7 +293,7 @@ export class ConsolidationRunCoordinator {
|
|
|
293
293
|
log.info(`cleaned ${deletedCommitments.length} expired commitments`);
|
|
294
294
|
if (resolveIndexingCapabilities(config).queryAwareIndexing) {
|
|
295
295
|
for (const m of deletedCommitments) {
|
|
296
|
-
|
|
296
|
+
await deindexMemoryAsync(
|
|
297
297
|
config.memoryDir,
|
|
298
298
|
m.path,
|
|
299
299
|
m.frontmatter.created,
|
|
@@ -336,7 +336,7 @@ export class ConsolidationRunCoordinator {
|
|
|
336
336
|
log.info(`cleaned ${deletedTTL.length} TTL-expired memories`);
|
|
337
337
|
if (resolveIndexingCapabilities(config).queryAwareIndexing) {
|
|
338
338
|
for (const m of deletedTTL) {
|
|
339
|
-
|
|
339
|
+
await deindexMemoryAsync(
|
|
340
340
|
config.memoryDir,
|
|
341
341
|
m.path,
|
|
342
342
|
m.frontmatter.created,
|
|
@@ -27,7 +27,7 @@ import type { StorageManager } from "../index.js";
|
|
|
27
27
|
import type { ExtractionEngine } from "../extraction.js";
|
|
28
28
|
import { log } from "../logger.js";
|
|
29
29
|
import { resolveIndexingCapabilities } from "../capabilities.js";
|
|
30
|
-
import {
|
|
30
|
+
import { deindexMemoryAsync } from "../temporal-index.js";
|
|
31
31
|
|
|
32
32
|
/** Result type of {@link ContradictionLinkingCoordinator.checkForContradiction}. */
|
|
33
33
|
export interface ContradictionResult {
|
|
@@ -253,7 +253,7 @@ export class ContradictionLinkingCoordinator {
|
|
|
253
253
|
resolveIndexingCapabilities(config).queryAwareIndexing &&
|
|
254
254
|
contradiction.supersededPath
|
|
255
255
|
) {
|
|
256
|
-
|
|
256
|
+
await deindexMemoryAsync(
|
|
257
257
|
config.memoryDir,
|
|
258
258
|
contradiction.supersededPath,
|
|
259
259
|
contradiction.supersededCreated,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
resolveCreationMemoryCapabilities,
|
|
35
35
|
} from "../capabilities.js";
|
|
36
36
|
import { isActiveMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
|
|
37
|
-
import {
|
|
37
|
+
import { deindexMemoryAsync } from "../temporal-index.js";
|
|
38
38
|
import { extractTopics } from "../topics.js";
|
|
39
39
|
import { log } from "../logger.js";
|
|
40
40
|
import path from "node:path";
|
|
@@ -345,7 +345,7 @@ export class LifecyclePolicyCoordinator {
|
|
|
345
345
|
memory.path &&
|
|
346
346
|
memory.frontmatter?.created
|
|
347
347
|
) {
|
|
348
|
-
|
|
348
|
+
await deindexMemoryAsync(
|
|
349
349
|
this.config.memoryDir,
|
|
350
350
|
memory.path,
|
|
351
351
|
memory.frontmatter.created,
|
|
@@ -23,7 +23,7 @@ import { ContentHashIndex, StorageManager } from "../index.js";
|
|
|
23
23
|
import { log } from "../logger.js";
|
|
24
24
|
import { isActiveMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
|
|
25
25
|
import { stripCitationForTemplate } from "../source-attribution.js";
|
|
26
|
-
import {
|
|
26
|
+
import { clearIndexesAsync, indexMemoriesBatchAsync, indexesExistAsync } from "../temporal-index.js";
|
|
27
27
|
import { normalizeSupersessionKey } from "../temporal-supersession.js";
|
|
28
28
|
import type { MemoryFile, MemoryFrontmatter, PluginConfig } from "../types.js";
|
|
29
29
|
import {
|
|
@@ -392,7 +392,7 @@ export class PersistenceIndexCoordinator {
|
|
|
392
392
|
return;
|
|
393
393
|
// Check for missing indexes BEFORE the early-return so first-time enablement
|
|
394
394
|
// can bootstrap the full corpus even when this extraction turn persisted nothing.
|
|
395
|
-
const needsFullRebuild = !
|
|
395
|
+
const needsFullRebuild = !(await indexesExistAsync(this.deps.config.memoryDir));
|
|
396
396
|
if (!needsFullRebuild && persistedIds.length === 0) return;
|
|
397
397
|
try {
|
|
398
398
|
// Read the corpus once to avoid N separate full-corpus scans.
|
|
@@ -449,17 +449,17 @@ export class PersistenceIndexCoordinator {
|
|
|
449
449
|
if (needsFullRebuild) {
|
|
450
450
|
// Always write empty indexes on full rebuild — even when the active pool
|
|
451
451
|
// is empty (e.g. store contains only archived/superseded entries).
|
|
452
|
-
// This marks bootstrap completion so
|
|
452
|
+
// This marks bootstrap completion so indexesExistAsync() returns true and
|
|
453
453
|
// subsequent extractions skip the full-corpus scan.
|
|
454
|
-
|
|
454
|
+
await clearIndexesAsync(this.deps.config.memoryDir);
|
|
455
455
|
if (entries.length > 0) {
|
|
456
|
-
|
|
456
|
+
await indexMemoriesBatchAsync(this.deps.config.memoryDir, entries);
|
|
457
457
|
}
|
|
458
458
|
log.info(
|
|
459
459
|
`temporal-index: bootstrapped from ${entries.length} active memories`,
|
|
460
460
|
);
|
|
461
461
|
} else if (entries.length > 0) {
|
|
462
|
-
|
|
462
|
+
await indexMemoriesBatchAsync(this.deps.config.memoryDir, entries);
|
|
463
463
|
}
|
|
464
464
|
} catch (err) {
|
|
465
465
|
log.debug(`temporal-index update failed (non-fatal): ${err}`);
|
|
@@ -33,7 +33,7 @@ import { RerankCache, rerankLocalOrNoop } from "../rerank.js";
|
|
|
33
33
|
import type { SearchBackend, SearchDegradation, SearchExecutionOptions, SearchQueryOptions } from "../search/port.js";
|
|
34
34
|
import { SecureStoreLockedError } from "../secure-store/index.js";
|
|
35
35
|
import { isPathInsideStorageRoot } from "../storage-paths.js";
|
|
36
|
-
import { extractTagsFromPrompt, isTemporalQuery, queryByDateRangeAsync, queryByTagsAsync, recencyWindowFromPrompt, resolvePromptTagPrefilterAsync } from "../temporal-index.js";
|
|
36
|
+
import { extractTagsFromPrompt, isTemporalQuery, queryByDateRangeAsync, queryByTagsAsync, readIndexSnapshotAsync, recencyWindowFromPrompt, resolvePromptTagPrefilterAsync } from "../temporal-index.js";
|
|
37
37
|
import { shouldFilterSupersededFromRecall } from "../temporal-supersession.js";
|
|
38
38
|
import { isValidAsOf, isValidityExpiredNow } from "../temporal-validity.js";
|
|
39
39
|
import type { TrustStageResultItem } from "../trust-score-stage.js";
|
|
@@ -305,18 +305,26 @@ export class RecallSearchPipelineCoordinator {
|
|
|
305
305
|
const temporalFromDate = isTemporalQuery(prompt)
|
|
306
306
|
? recencyWindowFromPrompt(prompt, Date.now())
|
|
307
307
|
: null;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
308
|
+
// Read the temporal + tag prefilter indexes as ONE consistent snapshot
|
|
309
|
+
// (issue #1911, Codex Medium) so a concurrent async index mutation can't be
|
|
310
|
+
// observed half-applied (new temporal row, tag membership not yet written),
|
|
311
|
+
// which would drop the current memory from the tag prefilter.
|
|
312
|
+
const [rawTemporal, tagSignals] = await readIndexSnapshotAsync(
|
|
313
|
+
this.deps.config.memoryDir,
|
|
314
|
+
() =>
|
|
315
|
+
Promise.all([
|
|
316
|
+
temporalFromDate
|
|
317
|
+
? queryByDateRangeAsync(this.deps.config.memoryDir, temporalFromDate)
|
|
318
|
+
: Promise.resolve<Set<string> | null>(null),
|
|
319
|
+
resolvePromptTagPrefilterAsync(this.deps.config.memoryDir, prompt).catch(
|
|
320
|
+
() => ({
|
|
321
|
+
matchedTags: extractTagsFromPrompt(prompt),
|
|
322
|
+
expandedTags: extractTagsFromPrompt(prompt),
|
|
323
|
+
paths: null,
|
|
324
|
+
}),
|
|
325
|
+
),
|
|
326
|
+
]),
|
|
327
|
+
);
|
|
320
328
|
|
|
321
329
|
const temporalCandidates = this.deps.scopeQueryAwarePaths(
|
|
322
330
|
rawTemporal,
|
|
@@ -1516,14 +1524,21 @@ export class RecallSearchPipelineCoordinator {
|
|
|
1516
1524
|
promptTags = extractTagsFromPrompt(prompt);
|
|
1517
1525
|
}
|
|
1518
1526
|
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
+
// Consistent temporal+tag snapshot (issue #1911, Codex Medium): read both
|
|
1528
|
+
// indexes under one op-lock so an in-flight async mutation is never observed
|
|
1529
|
+
// half-applied.
|
|
1530
|
+
const [rawTemporal, rawTags] = await readIndexSnapshotAsync(
|
|
1531
|
+
this.deps.config.memoryDir,
|
|
1532
|
+
() =>
|
|
1533
|
+
Promise.all([
|
|
1534
|
+
temporalFromDate !== null
|
|
1535
|
+
? queryByDateRangeAsync(this.deps.config.memoryDir, temporalFromDate)
|
|
1536
|
+
: Promise.resolve<Set<string> | null>(null),
|
|
1537
|
+
promptTags.length > 0
|
|
1538
|
+
? queryByTagsAsync(this.deps.config.memoryDir, promptTags)
|
|
1539
|
+
: Promise.resolve<Set<string> | null>(null),
|
|
1540
|
+
]),
|
|
1541
|
+
);
|
|
1527
1542
|
|
|
1528
1543
|
const queryIntent =
|
|
1529
1544
|
resolveConversationContextCapabilities(this.deps.config).intentRouting && prompt
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
gatewayTaskChainOptions,
|
|
43
43
|
} from "../fallback-llm.js";
|
|
44
44
|
import { resolveIndexingCapabilities, resolveConsolidationCapabilities } from "../capabilities.js";
|
|
45
|
-
import {
|
|
45
|
+
import { deindexMemoryAsync } from "../temporal-index.js";
|
|
46
46
|
import { runPeerProfileReasoner } from "../peers/index.js";
|
|
47
47
|
import type { StorageManager } from "../index.js";
|
|
48
48
|
import type { LocalLlmClient } from "../local-llm.js";
|
|
@@ -342,7 +342,7 @@ export class SemanticConsolidationCoordinator {
|
|
|
342
342
|
m.path &&
|
|
343
343
|
m.frontmatter?.created
|
|
344
344
|
) {
|
|
345
|
-
|
|
345
|
+
await deindexMemoryAsync(
|
|
346
346
|
targetStorage.dir,
|
|
347
347
|
m.path,
|
|
348
348
|
m.frontmatter.created,
|
package/src/orchestrator.ts
CHANGED
|
@@ -326,10 +326,6 @@ import {
|
|
|
326
326
|
} from "./lifecycle.js";
|
|
327
327
|
import { isActiveMemoryStatus } from "./memory-lifecycle-ledger-utils.js";
|
|
328
328
|
import {
|
|
329
|
-
indexMemoriesBatch,
|
|
330
|
-
clearIndexes,
|
|
331
|
-
indexesExist,
|
|
332
|
-
deindexMemory,
|
|
333
329
|
queryByDateRangeAsync,
|
|
334
330
|
queryByTagsAsync,
|
|
335
331
|
isTemporalQuery,
|