@gmickel/gno 1.40.0 → 1.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/assets/skill/SKILL.md +22 -1
- package/assets/skill/cli-reference.md +48 -0
- package/assets/skill/mcp-reference.md +31 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.40.0.zip → gno-browser-clipper-v1.42.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.42.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +3 -2
- package/spec/cli.md +146 -7
- package/spec/db/schema.sql +17 -0
- package/spec/mcp.md +350 -6
- package/spec/output-schemas/memory-recall.schema.json +159 -0
- package/spec/output-schemas/memory-remember.schema.json +164 -0
- package/spec/output-schemas/status.schema.json +269 -54
- package/src/cli/commands/daemon.ts +1 -0
- package/src/cli/commands/mcp.ts +3 -1
- package/src/cli/commands/memory.ts +491 -0
- package/src/cli/commands/status.ts +23 -4
- package/src/cli/options.ts +4 -0
- package/src/cli/program.ts +155 -0
- package/src/config/types.ts +10 -0
- package/src/core/audit-provenance.ts +91 -0
- package/src/core/audit-workspace.ts +17 -0
- package/src/core/connector-verifier.ts +2 -4
- package/src/core/memory-diagnostics.ts +144 -0
- package/src/core/memory-fence.ts +239 -0
- package/src/core/memory-recall.ts +269 -0
- package/src/core/memory-record.ts +435 -0
- package/src/core/memory-remember.ts +425 -0
- package/src/core/memory-types.ts +211 -0
- package/src/core/memory.ts +87 -0
- package/src/ingestion/sync.ts +17 -0
- package/src/mcp/AGENTS.md +7 -1
- package/src/mcp/CLAUDE.md +7 -1
- package/src/mcp/context.ts +37 -7
- package/src/mcp/http-egress.ts +2 -0
- package/src/mcp/http-modern.ts +214 -0
- package/src/mcp/http-security.ts +5 -0
- package/src/mcp/http-session.ts +4 -3
- package/src/mcp/http-transport.ts +81 -12
- package/src/mcp/resources/index.ts +3 -6
- package/src/mcp/server.ts +18 -16
- package/src/mcp/stdio-serving.ts +45 -0
- package/src/mcp/tool-descriptions-core.ts +56 -0
- package/src/mcp/tool-profile.ts +112 -0
- package/src/mcp/tools/index.ts +286 -126
- package/src/mcp/tools/memory-recall.ts +122 -0
- package/src/mcp/tools/memory-remember.ts +177 -0
- package/src/mcp/tools/memory-shared.ts +86 -0
- package/src/pipeline/search.ts +2 -0
- package/src/pipeline/types.ts +8 -0
- package/src/sdk/client.ts +94 -1
- package/src/sdk/index.ts +13 -0
- package/src/sdk/types.ts +28 -0
- package/src/serve/routes/api.ts +167 -0
- package/src/serve/routes/mcp.ts +1 -0
- package/src/serve/server.ts +27 -0
- package/src/store/migrations/027-memory-scopes.ts +37 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +127 -3
- package/src/store/types.ts +54 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.40.0.zip.sha256 +0 -1
package/src/cli/program.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
withCliWriteLease,
|
|
30
30
|
type WriteLeaseBusyFailure,
|
|
31
31
|
} from "../core/write-lease";
|
|
32
|
+
import { type McpToolProfile, parseMcpToolProfile } from "../mcp/tool-profile";
|
|
32
33
|
import { setColorsEnabled } from "./colors";
|
|
33
34
|
import {
|
|
34
35
|
applyGlobalOptions,
|
|
@@ -354,6 +355,7 @@ export function createProgram(): Command {
|
|
|
354
355
|
wireSearchCommands(program);
|
|
355
356
|
wireOnboardingCommands(program);
|
|
356
357
|
wireCaptureCommand(program);
|
|
358
|
+
wireMemoryCommands(program);
|
|
357
359
|
wireManagementCommands(program);
|
|
358
360
|
wireTraceCommands(program);
|
|
359
361
|
wirePublishCommand(program);
|
|
@@ -1888,6 +1890,132 @@ function wireCaptureCommand(program: Command): void {
|
|
|
1888
1890
|
);
|
|
1889
1891
|
}
|
|
1890
1892
|
|
|
1893
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1894
|
+
// Memory Commands (remember, recall)
|
|
1895
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1896
|
+
|
|
1897
|
+
function wireMemoryCommands(program: Command): void {
|
|
1898
|
+
program
|
|
1899
|
+
.command("remember <text>")
|
|
1900
|
+
.description("Store a fact in a memory-managed collection")
|
|
1901
|
+
.option("-c, --collection <name>", "memory-managed target collection")
|
|
1902
|
+
.option(
|
|
1903
|
+
"--scope <scope>",
|
|
1904
|
+
"explicit scope (repeatable, required; no implicit global scope)",
|
|
1905
|
+
collectRepeatableValue,
|
|
1906
|
+
[]
|
|
1907
|
+
)
|
|
1908
|
+
.option("--decision <decision>", "add or supersede (omit for candidates)")
|
|
1909
|
+
.option("--add", "shorthand for --decision add")
|
|
1910
|
+
.option(
|
|
1911
|
+
"--supersede <uri>",
|
|
1912
|
+
"shorthand for --decision supersede --predecessor <uri>"
|
|
1913
|
+
)
|
|
1914
|
+
.option("--predecessor <uri>", "predecessor gno:// URI for supersede")
|
|
1915
|
+
.option(
|
|
1916
|
+
"--predecessor-hash <hash>",
|
|
1917
|
+
"predecessor contentHash from recall (required for supersede)"
|
|
1918
|
+
)
|
|
1919
|
+
.option(
|
|
1920
|
+
"--receipt <path>",
|
|
1921
|
+
"recall receipt JSON to fence against (recall --json output)"
|
|
1922
|
+
)
|
|
1923
|
+
.option(
|
|
1924
|
+
"--derived-from <uri>",
|
|
1925
|
+
"declared origin of the fact (repeatable; gno:// origins are fenced)",
|
|
1926
|
+
collectRepeatableValue,
|
|
1927
|
+
[]
|
|
1928
|
+
)
|
|
1929
|
+
.option("--source <text>", "free-form source evidence")
|
|
1930
|
+
.option(
|
|
1931
|
+
"--caller <id>",
|
|
1932
|
+
"caller identity (default: $GNO_MEMORY_CALLER or cli:<user>)"
|
|
1933
|
+
)
|
|
1934
|
+
.option(
|
|
1935
|
+
"--session <id>",
|
|
1936
|
+
"session identity (default: $GNO_MEMORY_SESSION or ppid:<pid>)"
|
|
1937
|
+
)
|
|
1938
|
+
.option("--json", "JSON output")
|
|
1939
|
+
.action(async (text: string, cmdOpts: Record<string, unknown>) => {
|
|
1940
|
+
const format = getFormat(cmdOpts);
|
|
1941
|
+
const globals = getGlobals();
|
|
1942
|
+
const { formatRememberResult, remember } =
|
|
1943
|
+
await import("./commands/memory");
|
|
1944
|
+
const result = await remember({
|
|
1945
|
+
configPath: globals.config,
|
|
1946
|
+
indexName: globals.index,
|
|
1947
|
+
text,
|
|
1948
|
+
collection: cmdOpts.collection as string | undefined,
|
|
1949
|
+
scopes: cmdOpts.scope as string[],
|
|
1950
|
+
decision: cmdOpts.decision as string | undefined,
|
|
1951
|
+
add: Boolean(cmdOpts.add),
|
|
1952
|
+
supersede: cmdOpts.supersede as string | undefined,
|
|
1953
|
+
predecessor: cmdOpts.predecessor as string | undefined,
|
|
1954
|
+
predecessorHash: cmdOpts.predecessorHash as string | undefined,
|
|
1955
|
+
receipt: cmdOpts.receipt as string | undefined,
|
|
1956
|
+
derivedFrom: cmdOpts.derivedFrom as string[],
|
|
1957
|
+
source: cmdOpts.source as string | undefined,
|
|
1958
|
+
caller: cmdOpts.caller as string | undefined,
|
|
1959
|
+
session: cmdOpts.session as string | undefined,
|
|
1960
|
+
});
|
|
1961
|
+
await writeOutput(
|
|
1962
|
+
formatRememberResult(result, {
|
|
1963
|
+
json: format === "json",
|
|
1964
|
+
quiet: globals.quiet,
|
|
1965
|
+
}),
|
|
1966
|
+
format
|
|
1967
|
+
);
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1970
|
+
program
|
|
1971
|
+
.command("recall <query>")
|
|
1972
|
+
.description("Recall current facts from a memory-managed collection")
|
|
1973
|
+
.option("-c, --collection <name>", "memory-managed collection")
|
|
1974
|
+
.option(
|
|
1975
|
+
"--scope <scope>",
|
|
1976
|
+
"explicit scope (repeatable, required; no implicit global scope)",
|
|
1977
|
+
collectRepeatableValue,
|
|
1978
|
+
[]
|
|
1979
|
+
)
|
|
1980
|
+
.option("--max-facts <n>", "budget override: max facts (default 8)")
|
|
1981
|
+
.option(
|
|
1982
|
+
"--max-tokens <n>",
|
|
1983
|
+
"budget override: max payload tokens (default 512)"
|
|
1984
|
+
)
|
|
1985
|
+
.option(
|
|
1986
|
+
"--caller <id>",
|
|
1987
|
+
"caller identity (default: $GNO_MEMORY_CALLER or cli:<user>)"
|
|
1988
|
+
)
|
|
1989
|
+
.option(
|
|
1990
|
+
"--session <id>",
|
|
1991
|
+
"session identity (default: $GNO_MEMORY_SESSION or ppid:<pid>)"
|
|
1992
|
+
)
|
|
1993
|
+
.option("--json", "JSON output")
|
|
1994
|
+
.action(async (query: string, cmdOpts: Record<string, unknown>) => {
|
|
1995
|
+
const format = getFormat(cmdOpts);
|
|
1996
|
+
const globals = getGlobals();
|
|
1997
|
+
const { formatRecallResult, recall } = await import("./commands/memory");
|
|
1998
|
+
const result = await recall({
|
|
1999
|
+
configPath: globals.config,
|
|
2000
|
+
indexName: globals.index,
|
|
2001
|
+
query,
|
|
2002
|
+
collection: cmdOpts.collection as string | undefined,
|
|
2003
|
+
scopes: cmdOpts.scope as string[],
|
|
2004
|
+
maxFacts: cmdOpts.maxFacts as number | undefined,
|
|
2005
|
+
maxTokens: cmdOpts.maxTokens as number | undefined,
|
|
2006
|
+
caller: cmdOpts.caller as string | undefined,
|
|
2007
|
+
session: cmdOpts.session as string | undefined,
|
|
2008
|
+
});
|
|
2009
|
+
await writeOutput(
|
|
2010
|
+
formatRecallResult(result, {
|
|
2011
|
+
json: format === "json",
|
|
2012
|
+
quiet: globals.quiet,
|
|
2013
|
+
}),
|
|
2014
|
+
format
|
|
2015
|
+
);
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
|
|
1891
2019
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1892
2020
|
// Retrieval Commands (get, multi-get, ls)
|
|
1893
2021
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -2061,12 +2189,17 @@ function wireMcpCommand(program: Command): void {
|
|
|
2061
2189
|
"--enable-write",
|
|
2062
2190
|
"Enable write operations (capture, add-collection, sync, remove-collection)"
|
|
2063
2191
|
)
|
|
2192
|
+
.option(
|
|
2193
|
+
"--tool-profile <profile>",
|
|
2194
|
+
"advertised tool set: core (7 read tools + capture/remember with --enable-write) or full (default)"
|
|
2195
|
+
)
|
|
2064
2196
|
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
2065
2197
|
const { mcpCommand } = await import("./commands/mcp.js");
|
|
2066
2198
|
const globalOpts = program.opts();
|
|
2067
2199
|
const globals = parseGlobalOptions(globalOpts);
|
|
2068
2200
|
await mcpCommand(globals, {
|
|
2069
2201
|
enableWrite: cmdOpts.enableWrite === true ? true : undefined,
|
|
2202
|
+
toolProfile: parseToolProfileOption(cmdOpts.toolProfile),
|
|
2070
2203
|
});
|
|
2071
2204
|
});
|
|
2072
2205
|
|
|
@@ -4079,9 +4212,25 @@ function addGatewayOptions(
|
|
|
4079
4212
|
.option(
|
|
4080
4213
|
"--mcp-enable-write",
|
|
4081
4214
|
"authorize HTTP MCP mutation tools (independent of authentication)"
|
|
4215
|
+
)
|
|
4216
|
+
.option(
|
|
4217
|
+
"--mcp-tool-profile <profile>",
|
|
4218
|
+
"advertised MCP tool set: core or full (overrides gateway.toolProfile; default full)"
|
|
4082
4219
|
);
|
|
4083
4220
|
}
|
|
4084
4221
|
|
|
4222
|
+
/** Validate a `--tool-profile` / `--mcp-tool-profile` value; undefined when absent. */
|
|
4223
|
+
function parseToolProfileOption(value: unknown): McpToolProfile | undefined {
|
|
4224
|
+
try {
|
|
4225
|
+
return parseMcpToolProfile(value);
|
|
4226
|
+
} catch (error) {
|
|
4227
|
+
throw new CliError(
|
|
4228
|
+
"VALIDATION",
|
|
4229
|
+
error instanceof Error ? error.message : String(error)
|
|
4230
|
+
);
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4085
4234
|
function wireDaemonCommand(program: Command): void {
|
|
4086
4235
|
const daemonCmd = addGatewayOptions(
|
|
4087
4236
|
program
|
|
@@ -4160,6 +4309,8 @@ async function handleDaemonAction(
|
|
|
4160
4309
|
cmd: Command
|
|
4161
4310
|
): Promise<void> {
|
|
4162
4311
|
const globals = getGlobals();
|
|
4312
|
+
// Validate before any side effect (pid files, detach, runtime boot).
|
|
4313
|
+
const toolProfile = parseToolProfileOption(cmdOpts.mcpToolProfile);
|
|
4163
4314
|
|
|
4164
4315
|
const {
|
|
4165
4316
|
resolveProcessPaths,
|
|
@@ -4251,6 +4402,7 @@ async function handleDaemonAction(
|
|
|
4251
4402
|
allowedHosts: cmdOpts.mcpAllowedHost as string[] | undefined,
|
|
4252
4403
|
allowedOrigins: cmdOpts.mcpAllowedOrigin as string[] | undefined,
|
|
4253
4404
|
enableWrite: cmdOpts.mcpEnableWrite === true ? true : undefined,
|
|
4405
|
+
toolProfile,
|
|
4254
4406
|
});
|
|
4255
4407
|
if (!result.success) {
|
|
4256
4408
|
throw new CliError("RUNTIME", result.error);
|
|
@@ -4496,6 +4648,8 @@ async function handleServeAction(
|
|
|
4496
4648
|
cmd: Command
|
|
4497
4649
|
): Promise<void> {
|
|
4498
4650
|
const globals = getGlobals();
|
|
4651
|
+
// Validate before any side effect (NODE_ENV, pid files, detach, runtime boot).
|
|
4652
|
+
const toolProfile = parseToolProfileOption(cmdOpts.mcpToolProfile);
|
|
4499
4653
|
// NB: do NOT parse --port here. The status/stop branches don't need it
|
|
4500
4654
|
// and rejecting `gno serve --status --port nope` on irrelevant input
|
|
4501
4655
|
// would break management commands. parsePositiveInt is called inside
|
|
@@ -4596,6 +4750,7 @@ async function handleServeAction(
|
|
|
4596
4750
|
allowedHosts: cmdOpts.mcpAllowedHost as string[] | undefined,
|
|
4597
4751
|
allowedOrigins: cmdOpts.mcpAllowedOrigin as string[] | undefined,
|
|
4598
4752
|
enableWrite: cmdOpts.mcpEnableWrite === true ? true : undefined,
|
|
4753
|
+
toolProfile,
|
|
4599
4754
|
});
|
|
4600
4755
|
|
|
4601
4756
|
if (!result.success) {
|
package/src/config/types.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { z } from "zod";
|
|
|
11
11
|
|
|
12
12
|
import { URI_PREFIX } from "../app/constants";
|
|
13
13
|
import { JsonlFieldMappingSchema } from "../converters/adapters/jsonl/config";
|
|
14
|
+
import { MCP_TOOL_PROFILES } from "../mcp/tool-profile";
|
|
14
15
|
import { RetrievalTraceConfigSchema } from "./retrieval-traces";
|
|
15
16
|
|
|
16
17
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -163,6 +164,13 @@ export const CollectionSchema = z.object({
|
|
|
163
164
|
*/
|
|
164
165
|
sourceAvailability: SourceAvailabilitySchema.optional(),
|
|
165
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Declares the collection as a GNO-managed memory substrate: `remember`
|
|
169
|
+
* writes fact files here and refuses every collection without the flag.
|
|
170
|
+
* Omitted means false; ordinary retrieval is unaffected either way.
|
|
171
|
+
*/
|
|
172
|
+
memoryManaged: z.boolean().optional(),
|
|
173
|
+
|
|
166
174
|
/** Optional per-collection model overrides */
|
|
167
175
|
models: z
|
|
168
176
|
.object({
|
|
@@ -457,6 +465,8 @@ export const HttpGatewayConfigSchema = z.object({
|
|
|
457
465
|
allowedOrigins: z.array(z.string().min(1)).optional(),
|
|
458
466
|
/** Separate mutation authorization; authentication alone never enables it. */
|
|
459
467
|
enableWrite: z.boolean().optional(),
|
|
468
|
+
/** Advertised MCP tool set (`core` | `full`); read at listener start. */
|
|
469
|
+
toolProfile: z.enum(MCP_TOOL_PROFILES).optional(),
|
|
460
470
|
limits: HttpGatewayLimitsSchema.optional(),
|
|
461
471
|
});
|
|
462
472
|
|
|
@@ -5,6 +5,7 @@ import type { CaptureSource } from "./capture";
|
|
|
5
5
|
|
|
6
6
|
import { compareAuditFindingDrafts } from "./audit";
|
|
7
7
|
import { validateDeclaredCaptureProvenance } from "./capture";
|
|
8
|
+
import { diagnoseMemoryContent } from "./memory-diagnostics";
|
|
8
9
|
import {
|
|
9
10
|
hasDeclaredRecordProvenance,
|
|
10
11
|
validateDeclaredRecordProvenance,
|
|
@@ -20,6 +21,11 @@ export interface AuditProvenanceDocument {
|
|
|
20
21
|
captureSourceSupported?: boolean;
|
|
21
22
|
captureSource?: Partial<CaptureSource>;
|
|
22
23
|
captureSourceDeclared: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Present only for documents in a memory-managed collection: the file
|
|
26
|
+
* content the memory-record validator runs against (null when unreadable).
|
|
27
|
+
*/
|
|
28
|
+
memory?: { content: string | null };
|
|
23
29
|
record: {
|
|
24
30
|
recordKey?: string | null;
|
|
25
31
|
recordSourceLocator?: string | null;
|
|
@@ -52,6 +58,90 @@ const issueFinding = (input: {
|
|
|
52
58
|
],
|
|
53
59
|
});
|
|
54
60
|
|
|
61
|
+
const memoryFinding = (
|
|
62
|
+
document: AuditProvenanceDocument,
|
|
63
|
+
code: string,
|
|
64
|
+
message: string
|
|
65
|
+
): AuditFindingDraft => ({
|
|
66
|
+
subject: document.uri,
|
|
67
|
+
location: code,
|
|
68
|
+
severity: "warning",
|
|
69
|
+
message: `memory record excluded from recall: ${message}`,
|
|
70
|
+
evidence: [
|
|
71
|
+
{
|
|
72
|
+
kind: "memory-record-contract",
|
|
73
|
+
summary: code,
|
|
74
|
+
uri: document.uri,
|
|
75
|
+
path: document.relPath,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
guidance: [
|
|
79
|
+
"Repair the memory frontmatter (memory.recordId/scopes/caller/session/createdAt/contentHash) or re-create the fact with gno remember",
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Managed memory files are audited against the memory-record contract; each
|
|
85
|
+
* malformed file yields one finding per diagnostic code. Ordinary retrieval
|
|
86
|
+
* still sees such files; managed recall does not.
|
|
87
|
+
*/
|
|
88
|
+
export const evaluateMemoryRecordAudit = (
|
|
89
|
+
documents: readonly AuditProvenanceDocument[],
|
|
90
|
+
options: { truncated?: boolean } = {}
|
|
91
|
+
): AuditRuleContribution => {
|
|
92
|
+
const findings: AuditFindingDraft[] = [];
|
|
93
|
+
let managedDocuments = 0;
|
|
94
|
+
let unreadable = 0;
|
|
95
|
+
for (const document of documents) {
|
|
96
|
+
if (document.memory === undefined) continue;
|
|
97
|
+
managedDocuments += 1;
|
|
98
|
+
if (document.memory.content === null) {
|
|
99
|
+
unreadable += 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const diagnostics = diagnoseMemoryContent(document.memory.content) ?? [];
|
|
103
|
+
for (const diagnostic of diagnostics) {
|
|
104
|
+
findings.push(
|
|
105
|
+
memoryFinding(document, diagnostic.code, diagnostic.message)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
findings.sort(compareAuditFindingDrafts);
|
|
110
|
+
const truncated = options.truncated === true;
|
|
111
|
+
return {
|
|
112
|
+
ruleId: "provenance.memory-record",
|
|
113
|
+
category: "provenance",
|
|
114
|
+
status: truncated
|
|
115
|
+
? "inconclusive"
|
|
116
|
+
: unreadable > 0
|
|
117
|
+
? "unavailable"
|
|
118
|
+
: findings.length > 0
|
|
119
|
+
? "fail"
|
|
120
|
+
: managedDocuments === 0
|
|
121
|
+
? "skip"
|
|
122
|
+
: "pass",
|
|
123
|
+
message: truncated
|
|
124
|
+
? "Audit selection was truncated; memory record contract not fully evaluated"
|
|
125
|
+
: unreadable > 0
|
|
126
|
+
? `${unreadable} memory-managed source(s) could not be read`
|
|
127
|
+
: findings.length > 0
|
|
128
|
+
? `${findings.length} memory record contract violation(s) across ${managedDocuments} managed document(s)`
|
|
129
|
+
: managedDocuments === 0
|
|
130
|
+
? "No memory-managed collections in scope"
|
|
131
|
+
: `${managedDocuments} managed memory record(s) satisfy the contract`,
|
|
132
|
+
findings: findings.slice(0, PROVENANCE_AUDIT_MAX_FINDINGS_PER_RULE),
|
|
133
|
+
findingCount: findings.length,
|
|
134
|
+
examinedCount: managedDocuments,
|
|
135
|
+
skipReason: truncated
|
|
136
|
+
? "snapshot_truncated"
|
|
137
|
+
: unreadable > 0
|
|
138
|
+
? "source_unavailable"
|
|
139
|
+
: managedDocuments === 0
|
|
140
|
+
? "no_memory_managed_collections"
|
|
141
|
+
: null,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
55
145
|
/** Missing provenance is completeness evidence, never a truth judgment. */
|
|
56
146
|
export const evaluateProvenanceAudit = (
|
|
57
147
|
documents: readonly AuditProvenanceDocument[],
|
|
@@ -150,5 +240,6 @@ export const evaluateProvenanceAudit = (
|
|
|
150
240
|
recordFindings,
|
|
151
241
|
declaredRecordDocuments
|
|
152
242
|
),
|
|
243
|
+
evaluateMemoryRecordAudit(documents, options),
|
|
153
244
|
];
|
|
154
245
|
};
|
|
@@ -189,6 +189,7 @@ export const groupAuditPhysicalSources = (
|
|
|
189
189
|
const observeDocument = async (
|
|
190
190
|
document: DocumentRow,
|
|
191
191
|
roots: ReadonlyMap<string, string>,
|
|
192
|
+
managedCollections: ReadonlySet<string>,
|
|
192
193
|
readFrontmatter: boolean,
|
|
193
194
|
inspectFreshness: boolean,
|
|
194
195
|
signal?: AbortSignal
|
|
@@ -196,6 +197,8 @@ const observeDocument = async (
|
|
|
196
197
|
const markdownSource = MARKDOWN_SOURCE_EXTENSIONS.has(
|
|
197
198
|
document.sourceExt.toLowerCase()
|
|
198
199
|
);
|
|
200
|
+
const memoryManaged = managedCollections.has(document.collection);
|
|
201
|
+
const memoryUnreadable = memoryManaged ? { content: null } : undefined;
|
|
199
202
|
const root = roots.get(document.collection);
|
|
200
203
|
if (!root) {
|
|
201
204
|
return {
|
|
@@ -206,6 +209,7 @@ const observeDocument = async (
|
|
|
206
209
|
sourceState: "unreadable",
|
|
207
210
|
captureSourceSupported: markdownSource,
|
|
208
211
|
captureSourceDeclared: false,
|
|
212
|
+
memory: memoryUnreadable,
|
|
209
213
|
record: document,
|
|
210
214
|
},
|
|
211
215
|
freshness: {
|
|
@@ -238,6 +242,7 @@ const observeDocument = async (
|
|
|
238
242
|
sourceState: "missing",
|
|
239
243
|
captureSourceSupported: markdownSource,
|
|
240
244
|
captureSourceDeclared: false,
|
|
245
|
+
memory: memoryUnreadable,
|
|
241
246
|
record: document,
|
|
242
247
|
},
|
|
243
248
|
freshness: {
|
|
@@ -286,6 +291,11 @@ const observeDocument = async (
|
|
|
286
291
|
captureSource: incompleteFrontmatter
|
|
287
292
|
? undefined
|
|
288
293
|
: extractCaptureSourceFromFrontmatter(frontmatter),
|
|
294
|
+
// Memory facts are small single files, so the bounded frontmatter
|
|
295
|
+
// read covers the whole record the validator needs.
|
|
296
|
+
memory: memoryManaged
|
|
297
|
+
? { content: incompleteFrontmatter ? null : frontmatter }
|
|
298
|
+
: undefined,
|
|
289
299
|
record: document,
|
|
290
300
|
},
|
|
291
301
|
freshness: {
|
|
@@ -316,6 +326,7 @@ const observeDocument = async (
|
|
|
316
326
|
sourceState: "unreadable",
|
|
317
327
|
captureSourceSupported: markdownSource,
|
|
318
328
|
captureSourceDeclared: false,
|
|
329
|
+
memory: memoryUnreadable,
|
|
319
330
|
record: document,
|
|
320
331
|
},
|
|
321
332
|
freshness: {
|
|
@@ -382,11 +393,17 @@ const loadWorkspaceSnapshot = async (
|
|
|
382
393
|
const needsSourceObservation =
|
|
383
394
|
options.categories.includes("provenance") ||
|
|
384
395
|
options.categories.includes("freshness");
|
|
396
|
+
const managedCollections = new Set(
|
|
397
|
+
options.collections
|
|
398
|
+
.filter((collection) => collection.memoryManaged === true)
|
|
399
|
+
.map((collection) => collection.name)
|
|
400
|
+
);
|
|
385
401
|
const observed = needsSourceObservation
|
|
386
402
|
? await mapConcurrent(selected.documents, (document) =>
|
|
387
403
|
observeDocument(
|
|
388
404
|
document,
|
|
389
405
|
roots,
|
|
406
|
+
managedCollections,
|
|
390
407
|
options.categories.includes("provenance"),
|
|
391
408
|
options.categories.includes("freshness"),
|
|
392
409
|
options.signal
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** Read-only, privacy-bounded connector activation verification. */
|
|
2
2
|
|
|
3
|
-
import { Client } from "@modelcontextprotocol/
|
|
3
|
+
import { Client } from "@modelcontextprotocol/client";
|
|
4
4
|
import {
|
|
5
5
|
getDefaultEnvironment,
|
|
6
6
|
StdioClientTransport,
|
|
7
|
-
} from "@modelcontextprotocol/
|
|
7
|
+
} from "@modelcontextprotocol/client/stdio";
|
|
8
8
|
|
|
9
9
|
import type {
|
|
10
10
|
ActivationStageReceipt,
|
|
@@ -252,7 +252,6 @@ async function executeMcpProof(input: McpProofInput): Promise<McpProofResult> {
|
|
|
252
252
|
phase = "status";
|
|
253
253
|
const status = await client.callTool(
|
|
254
254
|
{ name: "gno_status", arguments: {} },
|
|
255
|
-
undefined,
|
|
256
255
|
requestOptions
|
|
257
256
|
);
|
|
258
257
|
if (
|
|
@@ -272,7 +271,6 @@ async function executeMcpProof(input: McpProofInput): Promise<McpProofResult> {
|
|
|
272
271
|
limit: 8,
|
|
273
272
|
},
|
|
274
273
|
},
|
|
275
|
-
undefined,
|
|
276
274
|
requestOptions
|
|
277
275
|
);
|
|
278
276
|
const searchFailed =
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Malformed-memory diagnostics projected through status and audit.
|
|
3
|
+
*
|
|
4
|
+
* A memory-managed collection may contain hand-edited files that no longer
|
|
5
|
+
* satisfy the record contract. Ingestion drops their scope rows (so managed
|
|
6
|
+
* recall never returns them) while ordinary retrieval still sees the file.
|
|
7
|
+
* This module names those files and their diagnostic codes.
|
|
8
|
+
*
|
|
9
|
+
* @module src/core/memory-diagnostics
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Collection } from "../config/types";
|
|
13
|
+
import type { StorePort } from "../store/types";
|
|
14
|
+
import type { MemoryDiagnostic } from "./memory-record";
|
|
15
|
+
|
|
16
|
+
import { validateMemoryRecord } from "./memory-record";
|
|
17
|
+
|
|
18
|
+
/** Bounded list of malformed files per collection in status output. */
|
|
19
|
+
export const MEMORY_STATUS_MAX_MALFORMED = 20;
|
|
20
|
+
|
|
21
|
+
export interface MalformedMemoryRecord {
|
|
22
|
+
uri: string;
|
|
23
|
+
relPath: string;
|
|
24
|
+
codes: string[];
|
|
25
|
+
diagnostics: MemoryDiagnostic[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MemoryCollectionStatus {
|
|
29
|
+
collection: string;
|
|
30
|
+
/** Active documents indexed in the collection. */
|
|
31
|
+
documents: number;
|
|
32
|
+
/** Documents that satisfy the record contract (recallable). */
|
|
33
|
+
records: number;
|
|
34
|
+
malformed: number;
|
|
35
|
+
malformedRecords: MalformedMemoryRecord[];
|
|
36
|
+
/** True when more malformed files exist than were listed. */
|
|
37
|
+
truncated: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MemoryStatus {
|
|
41
|
+
managedCollections: number;
|
|
42
|
+
records: number;
|
|
43
|
+
malformed: number;
|
|
44
|
+
collections: MemoryCollectionStatus[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const memoryManagedCollections = (
|
|
48
|
+
collections: readonly Collection[]
|
|
49
|
+
): Collection[] =>
|
|
50
|
+
collections.filter((collection) => collection.memoryManaged === true);
|
|
51
|
+
|
|
52
|
+
/** Validate one indexed file as a memory record (null when unreadable). */
|
|
53
|
+
export const diagnoseMemoryContent = (
|
|
54
|
+
content: string
|
|
55
|
+
): MemoryDiagnostic[] | null => {
|
|
56
|
+
const validation = validateMemoryRecord(content);
|
|
57
|
+
return validation.ok ? [] : validation.diagnostics;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Scan every active document of each memory-managed collection through the
|
|
62
|
+
* validator. Read-only; bounded by the collection's own document count.
|
|
63
|
+
*/
|
|
64
|
+
export async function buildMemoryStatus(
|
|
65
|
+
store: Pick<StorePort, "listDocuments" | "getContent">,
|
|
66
|
+
collections: readonly Collection[],
|
|
67
|
+
options: { maxListed?: number } = {}
|
|
68
|
+
): Promise<MemoryStatus> {
|
|
69
|
+
const maxListed = options.maxListed ?? MEMORY_STATUS_MAX_MALFORMED;
|
|
70
|
+
const statuses: MemoryCollectionStatus[] = [];
|
|
71
|
+
for (const collection of memoryManagedCollections(collections)) {
|
|
72
|
+
const listed = await store.listDocuments(collection.name);
|
|
73
|
+
const documents = listed.ok
|
|
74
|
+
? listed.value.filter((document) => document.active)
|
|
75
|
+
: [];
|
|
76
|
+
let records = 0;
|
|
77
|
+
const malformedRecords: MalformedMemoryRecord[] = [];
|
|
78
|
+
let malformed = 0;
|
|
79
|
+
for (const document of documents) {
|
|
80
|
+
if (!document.mirrorHash) continue;
|
|
81
|
+
const content = await store.getContent(document.mirrorHash);
|
|
82
|
+
if (!content.ok || content.value === null) continue;
|
|
83
|
+
const diagnostics = diagnoseMemoryContent(content.value);
|
|
84
|
+
if (diagnostics === null) continue;
|
|
85
|
+
if (diagnostics.length === 0) {
|
|
86
|
+
records += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
malformed += 1;
|
|
90
|
+
if (malformedRecords.length < maxListed) {
|
|
91
|
+
malformedRecords.push({
|
|
92
|
+
uri: document.uri,
|
|
93
|
+
relPath: document.relPath,
|
|
94
|
+
codes: [...new Set(diagnostics.map((item) => item.code))],
|
|
95
|
+
diagnostics,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
malformedRecords.sort((left, right) =>
|
|
100
|
+
left.uri < right.uri ? -1 : left.uri > right.uri ? 1 : 0
|
|
101
|
+
);
|
|
102
|
+
statuses.push({
|
|
103
|
+
collection: collection.name,
|
|
104
|
+
documents: documents.length,
|
|
105
|
+
records,
|
|
106
|
+
malformed,
|
|
107
|
+
malformedRecords,
|
|
108
|
+
truncated: malformed > malformedRecords.length,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
managedCollections: statuses.length,
|
|
113
|
+
records: statuses.reduce((sum, item) => sum + item.records, 0),
|
|
114
|
+
malformed: statuses.reduce((sum, item) => sum + item.malformed, 0),
|
|
115
|
+
collections: statuses,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Terminal lines for the `gno status` memory section. */
|
|
120
|
+
export function formatMemoryStatusLines(status: MemoryStatus): string[] {
|
|
121
|
+
if (status.managedCollections === 0) {
|
|
122
|
+
return ["Memory: no memory-managed collections"];
|
|
123
|
+
}
|
|
124
|
+
const lines = [
|
|
125
|
+
`Memory: ${status.records} records, ${status.malformed} malformed across ${status.managedCollections} managed collection${status.managedCollections === 1 ? "" : "s"}`,
|
|
126
|
+
];
|
|
127
|
+
for (const collection of status.collections) {
|
|
128
|
+
lines.push(
|
|
129
|
+
` ${collection.collection}: ${collection.records} records` +
|
|
130
|
+
(collection.malformed > 0
|
|
131
|
+
? `, ${collection.malformed} malformed (excluded from recall)`
|
|
132
|
+
: "")
|
|
133
|
+
);
|
|
134
|
+
for (const record of collection.malformedRecords) {
|
|
135
|
+
lines.push(` ${record.relPath}: ${record.codes.join(", ")}`);
|
|
136
|
+
}
|
|
137
|
+
if (collection.truncated) {
|
|
138
|
+
lines.push(
|
|
139
|
+
` ... ${collection.malformed - collection.malformedRecords.length} more (run: gno audit --category provenance)`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return lines;
|
|
144
|
+
}
|