@jungjaehoon/mama-core 1.8.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-graph/alias-write.js +3 -0
- package/dist/agent-graph/entity-resolve.js +3 -7
- package/dist/agent-graph/graph-query.js +5 -4
- package/dist/agent-graph/types.d.ts +30 -0
- package/dist/cases/store.d.ts +0 -4
- package/dist/cases/store.js +0 -66
- package/dist/cases/wiki-page-index.d.ts +0 -13
- package/dist/cases/wiki-page-index.js +0 -153
- package/dist/connectors/event-index.d.ts +1 -22
- package/dist/connectors/event-index.js +0 -157
- package/dist/context-compile/channel-grant.d.ts +46 -0
- package/dist/context-compile/channel-grant.js +66 -0
- package/dist/context-compile/compiler.js +5 -2
- package/dist/context-compile/index.d.ts +1 -0
- package/dist/context-compile/index.js +1 -0
- package/dist/context-compile/packet-store.d.ts +0 -1
- package/dist/context-compile/packet-store.js +0 -16
- package/dist/context-compile/source-readers.d.ts +1 -0
- package/dist/context-compile/source-readers.js +156 -23
- package/dist/context-compile/types.d.ts +17 -0
- package/dist/context-compile/visibility.js +28 -4
- package/dist/db-adapter/node-sqlite-adapter.js +4 -0
- package/dist/db-manager.d.ts +0 -13
- package/dist/db-manager.js +0 -66
- package/dist/decision-formatter.d.ts +0 -11
- package/dist/decision-formatter.js +0 -122
- package/dist/decision-tracker.d.ts +0 -12
- package/dist/decision-tracker.js +0 -50
- package/dist/edges/ref-validation.d.ts +1 -2
- package/dist/edges/ref-validation.js +33 -13
- package/dist/edges/store.d.ts +1 -3
- package/dist/edges/store.js +0 -214
- package/dist/edges/types.d.ts +10 -0
- package/dist/embedding-client.d.ts +0 -17
- package/dist/embedding-client.js +0 -80
- package/dist/embedding-server/mobile/auth.d.ts +1 -10
- package/dist/embedding-server/mobile/auth.js +0 -15
- package/dist/embeddings.d.ts +0 -12
- package/dist/embeddings.js +0 -68
- package/dist/entities/audit-metrics.d.ts +0 -5
- package/dist/entities/audit-metrics.js +0 -4
- package/dist/entities/candidate-generator.d.ts +1 -2
- package/dist/entities/candidate-generator.js +0 -412
- package/dist/entities/entity-list.d.ts +0 -1
- package/dist/entities/entity-list.js +0 -96
- package/dist/entities/entity-orphan-list.d.ts +0 -9
- package/dist/entities/entity-orphan-list.js +0 -88
- package/dist/entities/entity-search.d.ts +0 -1
- package/dist/entities/entity-search.js +0 -147
- package/dist/entities/errors.d.ts +0 -10
- package/dist/entities/errors.js +1 -25
- package/dist/entities/lineage-store.d.ts +0 -23
- package/dist/entities/lineage-store.js +0 -104
- package/dist/entities/policy-store.d.ts +0 -9
- package/dist/entities/policy-store.js +0 -188
- package/dist/entities/provenance-query.d.ts +0 -1
- package/dist/entities/provenance-query.js +0 -164
- package/dist/entities/resolution-engine.d.ts +1 -8
- package/dist/entities/resolution-engine.js +0 -66
- package/dist/entities/rollback-preview.d.ts +0 -4
- package/dist/entities/rollback-preview.js +0 -232
- package/dist/entities/store.d.ts +1 -10
- package/dist/entities/store.js +0 -49
- package/dist/index.d.ts +15 -31
- package/dist/index.js +4 -46
- package/dist/mama-api.d.ts +19 -1
- package/dist/mama-api.js +97 -2
- package/dist/memory/api.d.ts +9 -0
- package/dist/memory/api.js +68 -4
- package/dist/memory/event-store.d.ts +0 -1
- package/dist/memory/event-store.js +0 -15
- package/dist/memory/finding-store.d.ts +0 -1
- package/dist/memory/finding-store.js +0 -12
- package/dist/memory/scope-backfill.d.ts +0 -3
- package/dist/memory/scope-backfill.js +0 -74
- package/dist/memory/scope-store.d.ts +0 -1
- package/dist/memory/scope-store.js +0 -4
- package/dist/memory/truth-store.d.ts +0 -3
- package/dist/memory/truth-store.js +0 -22
- package/dist/memory/types.d.ts +0 -1
- package/dist/memory/types.js +0 -13
- package/dist/memory-store.d.ts +0 -16
- package/dist/memory-store.js +0 -51
- package/dist/ollama-client.d.ts +0 -9
- package/dist/ollama-client.js +0 -125
- package/dist/outcome-tracker.d.ts +0 -14
- package/dist/outcome-tracker.js +0 -44
- package/dist/progress-indicator.d.ts +0 -4
- package/dist/progress-indicator.js +0 -7
- package/dist/provenance/source-ref.d.ts +0 -9
- package/dist/provenance/source-ref.js +0 -145
- package/dist/query-intent.d.ts +0 -4
- package/dist/query-intent.js +0 -30
- package/dist/relevance-scorer.d.ts +0 -9
- package/dist/relevance-scorer.js +0 -81
- package/dist/search/feedback-store.d.ts +0 -1
- package/dist/search/feedback-store.js +0 -4
- package/dist/search/ranker-rescore.d.ts +0 -3
- package/dist/search/ranker-rescore.js +0 -27
- package/dist/search/ranker-trainer.d.ts +0 -8
- package/dist/search/ranker-trainer.js +0 -376
- package/dist/tier-validator.d.ts +0 -12
- package/dist/tier-validator.js +0 -56
- package/package.json +1 -10
- package/dist/cases/case-links.d.ts +0 -116
- package/dist/cases/case-links.js +0 -794
- package/dist/cases/composition-overrides.d.ts +0 -90
- package/dist/cases/composition-overrides.js +0 -724
- package/dist/cases/corrections.d.ts +0 -156
- package/dist/cases/corrections.js +0 -1045
- package/dist/cases/freshness.d.ts +0 -57
- package/dist/cases/freshness.js +0 -283
- package/dist/cases/live-state.d.ts +0 -48
- package/dist/cases/live-state.js +0 -256
- package/dist/cases/membership-explain.d.ts +0 -56
- package/dist/cases/membership-explain.js +0 -140
- package/dist/cases/membership-matcher.d.ts +0 -23
- package/dist/cases/membership-matcher.js +0 -193
- package/dist/cases/merge-split.d.ts +0 -61
- package/dist/cases/merge-split.js +0 -368
- package/dist/cases/role-inference.d.ts +0 -10
- package/dist/cases/role-inference.js +0 -85
- package/dist/cases/sqlite-transaction.d.ts +0 -10
- package/dist/cases/sqlite-transaction.js +0 -62
- package/dist/cases/target-ref.d.ts +0 -25
- package/dist/cases/target-ref.js +0 -22
- package/dist/cases/tombstone-sweeper.d.ts +0 -13
- package/dist/cases/tombstone-sweeper.js +0 -128
- package/dist/entities/entity-impact.d.ts +0 -41
- package/dist/entities/entity-impact.js +0 -140
- package/dist/entities/exact-merge-backfill.d.ts +0 -11
- package/dist/entities/exact-merge-backfill.js +0 -161
- package/dist/entities/lineage-backfill.d.ts +0 -12
- package/dist/entities/lineage-backfill.js +0 -101
- package/dist/memory-inject.d.ts +0 -24
- package/dist/memory-inject.js +0 -116
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The raw visibility rule, in one place.
|
|
3
|
+
*
|
|
4
|
+
* There were three copies of this decision. The reader had one, twin-ref validation had a
|
|
5
|
+
* second, and the standalone citation path had a third that carried a comment promising it
|
|
6
|
+
* reproduced the reader "clause for clause" - which it did, until the reader changed. The
|
|
7
|
+
* differential test that was supposed to catch that kept passing, because its fixtures
|
|
8
|
+
* never exercised the branch production takes. Copies pinned by a test are still copies;
|
|
9
|
+
* the test just tells you later.
|
|
10
|
+
*
|
|
11
|
+
* So the rule lives here, and everything that needs it takes it from here. It has to exist
|
|
12
|
+
* in two compiled forms - a boolean for checking one row you already hold, and SQL for
|
|
13
|
+
* selecting rows you do not - and those two forms are adjacent in this file with a
|
|
14
|
+
* differential over a matrix that includes what production sends. Two forms of one rule is
|
|
15
|
+
* the irreducible minimum; three copies of a decision is not.
|
|
16
|
+
*/
|
|
17
|
+
/** Connector -> the channels of it that may be read. */
|
|
18
|
+
export type ChannelGrant = Record<string, readonly string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* The rule: an event is visible when its connector is granted and its channel is one of
|
|
21
|
+
* the channels granted for that connector.
|
|
22
|
+
*
|
|
23
|
+
* A connector absent from the grant is denied. A connector present with an empty list is
|
|
24
|
+
* also denied - "granted the connector but no channel of it" is not a useful state, and
|
|
25
|
+
* reading it as "all channels" is how a grant becomes a wildcard.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isChannelGranted(connector: string, channel: string | null | undefined, grant: ChannelGrant): boolean;
|
|
28
|
+
export interface ChannelGrantClause {
|
|
29
|
+
sql: string;
|
|
30
|
+
params: unknown[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The same rule as a SQL predicate.
|
|
34
|
+
*
|
|
35
|
+
* Returns null when nothing is readable, which callers must treat as "select nothing"
|
|
36
|
+
* rather than "no filter" - an empty WHERE is how a visibility rule turns into a full scan
|
|
37
|
+
* at the exact moment it should have refused.
|
|
38
|
+
*
|
|
39
|
+
* `requestedConnectors` narrows further: a connector the caller did not ask for is not read
|
|
40
|
+
* even though it is granted. The grant is a ceiling, never an instruction.
|
|
41
|
+
*/
|
|
42
|
+
export declare function channelGrantClause(grant: ChannelGrant, requestedConnectors: readonly string[] | undefined, columns: {
|
|
43
|
+
connector: string;
|
|
44
|
+
channel: string;
|
|
45
|
+
}): ChannelGrantClause | null;
|
|
46
|
+
//# sourceMappingURL=channel-grant.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The raw visibility rule, in one place.
|
|
4
|
+
*
|
|
5
|
+
* There were three copies of this decision. The reader had one, twin-ref validation had a
|
|
6
|
+
* second, and the standalone citation path had a third that carried a comment promising it
|
|
7
|
+
* reproduced the reader "clause for clause" - which it did, until the reader changed. The
|
|
8
|
+
* differential test that was supposed to catch that kept passing, because its fixtures
|
|
9
|
+
* never exercised the branch production takes. Copies pinned by a test are still copies;
|
|
10
|
+
* the test just tells you later.
|
|
11
|
+
*
|
|
12
|
+
* So the rule lives here, and everything that needs it takes it from here. It has to exist
|
|
13
|
+
* in two compiled forms - a boolean for checking one row you already hold, and SQL for
|
|
14
|
+
* selecting rows you do not - and those two forms are adjacent in this file with a
|
|
15
|
+
* differential over a matrix that includes what production sends. Two forms of one rule is
|
|
16
|
+
* the irreducible minimum; three copies of a decision is not.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.isChannelGranted = isChannelGranted;
|
|
20
|
+
exports.channelGrantClause = channelGrantClause;
|
|
21
|
+
/**
|
|
22
|
+
* The rule: an event is visible when its connector is granted and its channel is one of
|
|
23
|
+
* the channels granted for that connector.
|
|
24
|
+
*
|
|
25
|
+
* A connector absent from the grant is denied. A connector present with an empty list is
|
|
26
|
+
* also denied - "granted the connector but no channel of it" is not a useful state, and
|
|
27
|
+
* reading it as "all channels" is how a grant becomes a wildcard.
|
|
28
|
+
*/
|
|
29
|
+
function isChannelGranted(connector, channel, grant) {
|
|
30
|
+
if (typeof channel !== 'string' || channel.length === 0)
|
|
31
|
+
return false;
|
|
32
|
+
const channels = grant[connector];
|
|
33
|
+
return Array.isArray(channels) && channels.includes(channel);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The same rule as a SQL predicate.
|
|
37
|
+
*
|
|
38
|
+
* Returns null when nothing is readable, which callers must treat as "select nothing"
|
|
39
|
+
* rather than "no filter" - an empty WHERE is how a visibility rule turns into a full scan
|
|
40
|
+
* at the exact moment it should have refused.
|
|
41
|
+
*
|
|
42
|
+
* `requestedConnectors` narrows further: a connector the caller did not ask for is not read
|
|
43
|
+
* even though it is granted. The grant is a ceiling, never an instruction.
|
|
44
|
+
*/
|
|
45
|
+
function channelGrantClause(grant, requestedConnectors, columns) {
|
|
46
|
+
const pairs = [];
|
|
47
|
+
const params = [];
|
|
48
|
+
for (const [connector, channels] of Object.entries(grant)) {
|
|
49
|
+
if (Array.isArray(requestedConnectors) && !requestedConnectors.includes(connector))
|
|
50
|
+
continue;
|
|
51
|
+
// Array.isArray, not `?? []`: the boolean form denies a malformed grant quietly, so
|
|
52
|
+
// this one must not throw on it. Two forms of a rule that disagree on bad input have
|
|
53
|
+
// already started to be two rules.
|
|
54
|
+
if (!Array.isArray(channels))
|
|
55
|
+
continue;
|
|
56
|
+
const unique = [
|
|
57
|
+
...new Set(channels.filter((channel) => typeof channel === 'string' && channel.length > 0)),
|
|
58
|
+
];
|
|
59
|
+
if (unique.length === 0)
|
|
60
|
+
continue;
|
|
61
|
+
pairs.push(`(${columns.connector} = ? AND ${columns.channel} IN (${unique.map(() => '?').join(', ')}))`);
|
|
62
|
+
params.push(connector, ...unique);
|
|
63
|
+
}
|
|
64
|
+
return pairs.length === 0 ? null : { sql: `(${pairs.join(' OR ')})`, params };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=channel-grant.js.map
|
|
@@ -152,7 +152,7 @@ function hasSeedRefVisibilityConstraints(input, boundary) {
|
|
|
152
152
|
input.range !== undefined ||
|
|
153
153
|
(input.as_of !== undefined && input.as_of !== null));
|
|
154
154
|
}
|
|
155
|
-
function assertSeedRefsVisibleToInput(adapter, input, seedRefs) {
|
|
155
|
+
function assertSeedRefsVisibleToInput(adapter, input, seedRefs, boundary) {
|
|
156
156
|
if (seedRefs.length === 0) {
|
|
157
157
|
return [];
|
|
158
158
|
}
|
|
@@ -179,6 +179,9 @@ function assertSeedRefsVisibleToInput(adapter, input, seedRefs) {
|
|
|
179
179
|
tenantId: input.tenant_id,
|
|
180
180
|
startMs: minVisibleMs(input),
|
|
181
181
|
asOfMs: maxVisibleMs(input),
|
|
182
|
+
// Without this a caller could seed a raw id from a channel the grant refuses and have
|
|
183
|
+
// its identifiers surface in the packet - a citation path reaching past the read path.
|
|
184
|
+
...(boundary?.channels ? { channels: boundary.channels } : {}),
|
|
182
185
|
});
|
|
183
186
|
for (const ref of seedRefs) {
|
|
184
187
|
if (ref.kind !== 'raw') {
|
|
@@ -278,7 +281,7 @@ async function compileContext(input, deps = {}) {
|
|
|
278
281
|
});
|
|
279
282
|
}
|
|
280
283
|
if (deps.adapter || hasSeedRefVisibilityConstraints(effectiveInput, boundary)) {
|
|
281
|
-
canonicalSeedRefs = assertSeedRefsVisibleToInput(deps.adapter, effectiveInput, seedRefs);
|
|
284
|
+
canonicalSeedRefs = assertSeedRefsVisibleToInput(deps.adapter, effectiveInput, seedRefs, boundary);
|
|
282
285
|
}
|
|
283
286
|
const canonicalScopes = (0, visibility_js_2.canonicalizeContextScopes)(effectiveInput.scopes);
|
|
284
287
|
const readInput = sourceInput(effectiveInput, boundary);
|
|
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./types.js"), exports);
|
|
18
18
|
__exportStar(require("./boundary-defaults.js"), exports);
|
|
19
|
+
__exportStar(require("./channel-grant.js"), exports);
|
|
19
20
|
__exportStar(require("./ref.js"), exports);
|
|
20
21
|
__exportStar(require("./visibility.js"), exports);
|
|
21
22
|
__exportStar(require("./packet-store.js"), exports);
|
|
@@ -3,7 +3,6 @@ import type { ContextPacketRecord, TrustedContextPacketLookupInput } from './typ
|
|
|
3
3
|
type ContextPacketAdapter = Pick<DatabaseAdapter, 'prepare'>;
|
|
4
4
|
export declare function insertContextPacket(adapter: ContextPacketAdapter, packet: ContextPacketRecord): ContextPacketRecord;
|
|
5
5
|
export declare function getContextPacket(adapter: ContextPacketAdapter, packetId: string): ContextPacketRecord | null;
|
|
6
|
-
export declare function listContextPacketsForModelRun(adapter: ContextPacketAdapter, modelRunId: string, limit?: number): ContextPacketRecord[];
|
|
7
6
|
export declare function getContextPacketForTrustedUse(adapter: ContextPacketAdapter, input: TrustedContextPacketLookupInput): ContextPacketRecord | null;
|
|
8
7
|
export {};
|
|
9
8
|
//# sourceMappingURL=packet-store.d.ts.map
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.insertContextPacket = insertContextPacket;
|
|
4
4
|
exports.getContextPacket = getContextPacket;
|
|
5
|
-
exports.listContextPacketsForModelRun = listContextPacketsForModelRun;
|
|
6
5
|
exports.getContextPacketForTrustedUse = getContextPacketForTrustedUse;
|
|
7
6
|
function parseJson(row, field) {
|
|
8
7
|
const value = row[field];
|
|
@@ -222,21 +221,6 @@ function getContextPacket(adapter, packetId) {
|
|
|
222
221
|
const row = adapter.prepare('SELECT * FROM context_packets WHERE packet_id = ?').get(packetId);
|
|
223
222
|
return row ? mapPacketRow(row) : null;
|
|
224
223
|
}
|
|
225
|
-
function listContextPacketsForModelRun(adapter, modelRunId, limit = 50) {
|
|
226
|
-
if (!Number.isFinite(limit) || limit <= 0) {
|
|
227
|
-
return [];
|
|
228
|
-
}
|
|
229
|
-
const rows = adapter
|
|
230
|
-
.prepare(`
|
|
231
|
-
SELECT *
|
|
232
|
-
FROM context_packets
|
|
233
|
-
WHERE model_run_id = ?
|
|
234
|
-
ORDER BY created_at DESC, packet_id DESC
|
|
235
|
-
LIMIT ?
|
|
236
|
-
`)
|
|
237
|
-
.all(modelRunId, Math.floor(limit));
|
|
238
|
-
return rows.map((row) => mapPacketRow(row));
|
|
239
|
-
}
|
|
240
224
|
function getContextPacketForTrustedUse(adapter, input) {
|
|
241
225
|
const packet = getContextPacket(adapter, input.packetId);
|
|
242
226
|
if (!packet) {
|
|
@@ -64,6 +64,7 @@ export interface ContextSourceReaderDeps {
|
|
|
64
64
|
export declare function sourceRefsFromCandidates(candidates: readonly ContextCandidate[]): ContextRef[];
|
|
65
65
|
export declare function readMemoryCandidates(input: ContextSourceReadInput, deps?: ContextSourceReaderDeps): Promise<ContextSourceReadResult>;
|
|
66
66
|
export declare function readRawCandidates(adapter: ContextSourceAdapter, input: ContextSourceReadInput): ContextSourceReadResult;
|
|
67
|
+
export declare function setContextSourceClockForTests(clock: () => number): void;
|
|
67
68
|
export declare function readGraphCandidates(adapter: ContextSourceAdapter, input: ContextSourceReadInput, visibleRefs: readonly ContextRef[], deps?: ContextSourceReaderDeps): ContextSourceReadResult;
|
|
68
69
|
export {};
|
|
69
70
|
//# sourceMappingURL=source-readers.d.ts.map
|
|
@@ -3,12 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.sourceRefsFromCandidates = sourceRefsFromCandidates;
|
|
4
4
|
exports.readMemoryCandidates = readMemoryCandidates;
|
|
5
5
|
exports.readRawCandidates = readRawCandidates;
|
|
6
|
+
exports.setContextSourceClockForTests = setContextSourceClockForTests;
|
|
6
7
|
exports.readGraphCandidates = readGraphCandidates;
|
|
7
8
|
const ref_validation_js_1 = require("../edges/ref-validation.js");
|
|
8
9
|
const api_js_1 = require("../memory/api.js");
|
|
9
10
|
const ref_js_1 = require("./ref.js");
|
|
10
11
|
const visibility_js_1 = require("./visibility.js");
|
|
11
12
|
const boundary_defaults_js_1 = require("./boundary-defaults.js");
|
|
13
|
+
const channel_grant_js_1 = require("./channel-grant.js");
|
|
12
14
|
function hiddenAggregate() {
|
|
13
15
|
return { total: 0, by_kind: {}, by_reason: {} };
|
|
14
16
|
}
|
|
@@ -504,6 +506,18 @@ function readRawCandidates(adapter, input) {
|
|
|
504
506
|
if (!tableExists(adapter, 'connector_event_index')) {
|
|
505
507
|
return resultFromCandidates([], hiddenAggregate());
|
|
506
508
|
}
|
|
509
|
+
// These guards hold on BOTH paths. An explicitly emptied project window and an empty
|
|
510
|
+
// scope list are deliberate "read nothing" requests, and the grant branch skipping them
|
|
511
|
+
// would have turned two fail-closed states into reads.
|
|
512
|
+
if ((effectiveInput.connectors ?? []).length === 0 ||
|
|
513
|
+
(effectiveInput.scopes ?? []).length === 0 ||
|
|
514
|
+
hasExplicitEmptyProjectWindow(effectiveInput)) {
|
|
515
|
+
return resultFromCandidates([], hiddenAggregate());
|
|
516
|
+
}
|
|
517
|
+
const channelGrant = effectiveInput.boundary?.channels;
|
|
518
|
+
if (channelGrant) {
|
|
519
|
+
return readRawCandidatesWithinGrant(adapter, effectiveInput, channelGrant);
|
|
520
|
+
}
|
|
507
521
|
const connectors = effectiveInput.connectors ?? [];
|
|
508
522
|
const scopes = effectiveInput.scopes ?? [];
|
|
509
523
|
const projectIds = (effectiveInput.project_refs ?? []).map((project) => project.id);
|
|
@@ -512,11 +526,6 @@ function readRawCandidates(adapter, input) {
|
|
|
512
526
|
projectIds.length > 0 ||
|
|
513
527
|
Boolean(effectiveInput.tenant_id);
|
|
514
528
|
const includesLegacyGlobalSystem = hasGlobalSystemScope && !hasScopedVisibility;
|
|
515
|
-
if (connectors.length === 0 ||
|
|
516
|
-
scopes.length === 0 ||
|
|
517
|
-
hasExplicitEmptyProjectWindow(effectiveInput)) {
|
|
518
|
-
return resultFromCandidates([], hiddenAggregate());
|
|
519
|
-
}
|
|
520
529
|
const clauses = [`source_connector IN (${placeholders(connectors)})`];
|
|
521
530
|
const params = [...connectors];
|
|
522
531
|
if (projectIds.length > 0) {
|
|
@@ -571,29 +580,150 @@ function readRawCandidates(adapter, input) {
|
|
|
571
580
|
LIMIT ?
|
|
572
581
|
`)
|
|
573
582
|
.all(...params);
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
583
|
+
return resultFromCandidates(rows.map(rawRowToCandidate), hiddenAggregate());
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Raw visibility decided by the caller's (connector, channel) grant.
|
|
587
|
+
*
|
|
588
|
+
* Two conditions, both necessary: the connector is granted, and the channel is one of the
|
|
589
|
+
* channels granted for it. Nothing else gates a row - not project, not tenant, not the
|
|
590
|
+
* derived scope columns. See ContextBoundary.channels for why those were removed rather
|
|
591
|
+
* than kept alongside: measured on the live index, they hide two thirds of the corpus from
|
|
592
|
+
* any caller precise enough to name a scope, and a reader that answers narrow questions
|
|
593
|
+
* from nothing is worse than one that answers them from the events.
|
|
594
|
+
*
|
|
595
|
+
* The grant is enforced in SQL rather than filtered afterwards, so `limit` counts rows the
|
|
596
|
+
* caller may actually see. Filtering after the limit silently turns "the 200 most recent
|
|
597
|
+
* events" into "however many of the 200 most recent events happened to be readable".
|
|
598
|
+
*/
|
|
599
|
+
function readRawCandidatesWithinGrant(adapter, effectiveInput, grant) {
|
|
600
|
+
const requested = effectiveInput.connectors;
|
|
601
|
+
if (Array.isArray(requested) && requested.length === 0) {
|
|
602
|
+
return resultFromCandidates([], hiddenAggregate());
|
|
603
|
+
}
|
|
604
|
+
// The rule is compiled to SQL in exactly one place (channel-grant.ts), which is also
|
|
605
|
+
// where its boolean form lives. Building the clause here a second time is how the three
|
|
606
|
+
// copies this replaces came about.
|
|
607
|
+
const granted = (0, channel_grant_js_1.channelGrantClause)(grant, requested, {
|
|
608
|
+
connector: 'source_connector',
|
|
609
|
+
channel: 'channel',
|
|
610
|
+
});
|
|
611
|
+
// No overlap between what was asked for and what is granted reads nothing. It must not
|
|
612
|
+
// fall through to an unfiltered scan.
|
|
613
|
+
if (granted === null) {
|
|
614
|
+
return resultFromCandidates([], refusedAggregate(countRefusedByGrant(adapter, effectiveInput)));
|
|
615
|
+
}
|
|
616
|
+
const clauses = [granted.sql];
|
|
617
|
+
const params = [...granted.params];
|
|
618
|
+
const min = minVisibleTimeMs(effectiveInput);
|
|
619
|
+
if (min !== null) {
|
|
620
|
+
clauses.push('COALESCE(event_datetime, source_timestamp_ms) >= ?');
|
|
621
|
+
params.push(min);
|
|
622
|
+
}
|
|
623
|
+
// EVIDENCE IS WHAT HAS ALREADY HAPPENED, so the window ends now unless the caller named
|
|
624
|
+
// a later end explicitly.
|
|
625
|
+
//
|
|
626
|
+
// Found by running this reader against a snapshot of the live index rather than against
|
|
627
|
+
// fixtures. `event_datetime` is when the event OCCURS, and a calendar entry occurs in
|
|
628
|
+
// the future: 2,762 live rows are future-dated, one of them in 2056. Ordering by that
|
|
629
|
+
// column descending gave them the entire top-100 - a report asking what happened in the
|
|
630
|
+
// last seven days received one hundred calendar entries and zero messages. Excluding
|
|
631
|
+
// them, the same query returns 99 messages and one calendar entry.
|
|
632
|
+
//
|
|
633
|
+
// Nothing caught this before because the predicate this replaces returned no rows at
|
|
634
|
+
// all, so its ordering never mattered. A caller that genuinely wants the future still
|
|
635
|
+
// gets it by setting range.end_ms or as_of past now.
|
|
636
|
+
const max = maxVisibleTimeMs(effectiveInput) ?? nowMs();
|
|
637
|
+
clauses.push('COALESCE(event_datetime, source_timestamp_ms) <= ?');
|
|
638
|
+
params.push(max);
|
|
639
|
+
params.push(normalizeLimit(effectiveInput.limit));
|
|
640
|
+
const rows = adapter
|
|
641
|
+
.prepare(`
|
|
642
|
+
SELECT event_index_id, source_connector, source_id, channel, title, content,
|
|
643
|
+
event_datetime, source_timestamp_ms
|
|
644
|
+
FROM connector_event_index
|
|
645
|
+
WHERE ${clauses.join('\n AND ')}
|
|
646
|
+
ORDER BY COALESCE(event_datetime, source_timestamp_ms) DESC, event_index_id ASC
|
|
647
|
+
LIMIT ?
|
|
648
|
+
`)
|
|
649
|
+
.all(...params);
|
|
650
|
+
return resultFromCandidates(rows.map(rawRowToCandidate), refusedAggregate(countRefusedByGrant(adapter, effectiveInput, granted)));
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* How many events the grant kept out.
|
|
654
|
+
*
|
|
655
|
+
* Without this the caller cannot tell "this channel was quiet" from "you are not allowed to
|
|
656
|
+
* see this channel", because the filtering happens in SQL and leaves no trace. That
|
|
657
|
+
* distinction is the entire point: an owner asking about a board whose events are all
|
|
658
|
+
* refused would otherwise get a confident empty answer with a clean diagnostics block.
|
|
659
|
+
*
|
|
660
|
+
* Counted in the requested connectors only - rows of connectors the run never asked about
|
|
661
|
+
* are not "refused", they are simply not in question.
|
|
662
|
+
*/
|
|
663
|
+
function countRefusedByGrant(adapter, effectiveInput, granted = null) {
|
|
664
|
+
const requested = effectiveInput.connectors ?? [];
|
|
665
|
+
if (requested.length === 0)
|
|
666
|
+
return 0;
|
|
667
|
+
const clauses = [`source_connector IN (${placeholders(requested)})`];
|
|
668
|
+
const params = [...requested];
|
|
669
|
+
if (granted !== null) {
|
|
670
|
+
// `NOT (connector = ? AND channel IN (...))` is NULL when channel is NULL, so a
|
|
671
|
+
// NULL-channel row is neither selected nor counted - it vanishes from both sides of
|
|
672
|
+
// the answer. A connector that does not populate channel would have every event
|
|
673
|
+
// refused and the count would report zero, which is precisely the confident empty
|
|
674
|
+
// answer this count exists to prevent.
|
|
675
|
+
clauses.push(`(channel IS NULL OR NOT ${granted.sql})`);
|
|
676
|
+
params.push(...granted.params);
|
|
677
|
+
}
|
|
678
|
+
const min = minVisibleTimeMs(effectiveInput);
|
|
679
|
+
const max = maxVisibleTimeMs(effectiveInput);
|
|
680
|
+
if (min !== null) {
|
|
681
|
+
clauses.push('COALESCE(event_datetime, source_timestamp_ms) >= ?');
|
|
682
|
+
params.push(min);
|
|
683
|
+
}
|
|
684
|
+
if (max !== null) {
|
|
685
|
+
clauses.push('COALESCE(event_datetime, source_timestamp_ms) <= ?');
|
|
686
|
+
params.push(max);
|
|
687
|
+
}
|
|
688
|
+
const row = adapter
|
|
689
|
+
.prepare(`SELECT COUNT(*) AS n FROM connector_event_index WHERE ${clauses.join(' AND ')}`)
|
|
690
|
+
.get(...params);
|
|
691
|
+
return Number(row?.n ?? 0);
|
|
692
|
+
}
|
|
693
|
+
/** Injectable only so a test can pin the boundary; production reads the clock. */
|
|
694
|
+
let nowMs = () => Date.now();
|
|
695
|
+
function setContextSourceClockForTests(clock) {
|
|
696
|
+
nowMs = clock;
|
|
697
|
+
}
|
|
698
|
+
function refusedAggregate(refused) {
|
|
699
|
+
const aggregate = hiddenAggregate();
|
|
700
|
+
if (refused > 0) {
|
|
701
|
+
aggregate.total = refused;
|
|
702
|
+
aggregate.by_kind.raw = refused;
|
|
703
|
+
aggregate.by_reason.channel_not_granted = refused;
|
|
704
|
+
}
|
|
705
|
+
return aggregate;
|
|
706
|
+
}
|
|
707
|
+
function rawRowToCandidate(row) {
|
|
708
|
+
const sourceId = typeof row.source_id === 'string' && row.source_id.trim().length > 0
|
|
709
|
+
? row.source_id
|
|
710
|
+
: undefined;
|
|
711
|
+
return {
|
|
712
|
+
ref: {
|
|
579
713
|
kind: 'raw',
|
|
580
714
|
connector: String(row.source_connector),
|
|
581
715
|
raw_id: String(row.event_index_id),
|
|
582
716
|
...(sourceId ? { source_id: sourceId } : {}),
|
|
583
717
|
channel_id: typeof row.channel === 'string' ? row.channel : null,
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
support: emptySupport('connector_event_index'),
|
|
594
|
-
};
|
|
595
|
-
});
|
|
596
|
-
return resultFromCandidates(candidates, hiddenAggregate());
|
|
718
|
+
},
|
|
719
|
+
title: String(row.title ?? 'Raw event'),
|
|
720
|
+
excerpt: String(row.content ?? '').slice(0, 500),
|
|
721
|
+
score: 0.7,
|
|
722
|
+
timestamp_ms: parseTimestampMs(row.event_datetime ?? row.source_timestamp_ms),
|
|
723
|
+
source: 'raw',
|
|
724
|
+
visible: true,
|
|
725
|
+
support: emptySupport('connector_event_index'),
|
|
726
|
+
};
|
|
597
727
|
}
|
|
598
728
|
function readGraphCandidates(adapter, input, visibleRefs, deps = {}) {
|
|
599
729
|
const effectiveInput = normalizeTimeFilters(applyBoundaryDefaults(input));
|
|
@@ -633,6 +763,9 @@ function readGraphCandidates(adapter, input, visibleRefs, deps = {}) {
|
|
|
633
763
|
startMs: min,
|
|
634
764
|
asOfMs: max,
|
|
635
765
|
limit: normalizeLimit(effectiveInput.limit),
|
|
766
|
+
// Graph neighbours carry an excerpt, so an ungranted raw row reached through an edge
|
|
767
|
+
// would disclose content the reader itself refuses.
|
|
768
|
+
...(effectiveInput.boundary?.channels ? { channels: effectiveInput.boundary.channels } : {}),
|
|
636
769
|
}).filter((edge) => (min === null || edge.created_at >= min) && (max === null || edge.created_at <= max));
|
|
637
770
|
const candidates = [];
|
|
638
771
|
const emitted = new Set();
|
|
@@ -49,6 +49,23 @@ export interface ContextBoundary {
|
|
|
49
49
|
tenant_id?: string | null;
|
|
50
50
|
as_of?: string | number | null;
|
|
51
51
|
range?: ContextRange;
|
|
52
|
+
/**
|
|
53
|
+
* Which channels of each connector may be read, keyed by connector.
|
|
54
|
+
*
|
|
55
|
+
* When present this DECIDES raw visibility, and the scope/project/tenant clauses are not
|
|
56
|
+
* applied to raw rows. That is a deliberate replacement, not a relaxation, and the reason
|
|
57
|
+
* is measurable: on a live index of 30,671 events, `memory_scope_id` is populated on 37%
|
|
58
|
+
* of rows in three mutually incompatible namespaces, and 63% carry no scope at all - so a
|
|
59
|
+
* caller that names any specific scope loses exactly the two thirds of the corpus that
|
|
60
|
+
* were never labelled. A filter that hides most of the data from anyone precise enough to
|
|
61
|
+
* ask a narrow question is not a privacy boundary; it is a leak of a different kind, the
|
|
62
|
+
* kind where the system quietly answers from nothing.
|
|
63
|
+
*
|
|
64
|
+
* The grant is (connector, channel) because that is what the owner actually configures
|
|
65
|
+
* and what the connector actually emits. The caller computes it - this module enforces
|
|
66
|
+
* what it is given and never widens it.
|
|
67
|
+
*/
|
|
68
|
+
channels?: Record<string, readonly string[]>;
|
|
52
69
|
}
|
|
53
70
|
export interface CanonicalContextScopes {
|
|
54
71
|
scopes: MemoryScopeRef[];
|
|
@@ -8,6 +8,7 @@ exports.normalizeSeedRefs = normalizeSeedRefs;
|
|
|
8
8
|
const node_crypto_1 = require("node:crypto");
|
|
9
9
|
const types_js_1 = require("../memory/types.js");
|
|
10
10
|
const ref_js_1 = require("./ref.js");
|
|
11
|
+
const channel_grant_js_1 = require("./channel-grant.js");
|
|
11
12
|
const SCOPE_ORDER = {
|
|
12
13
|
project: 0,
|
|
13
14
|
channel: 1,
|
|
@@ -110,13 +111,36 @@ function assertContextBoundaryAllowsInput(input) {
|
|
|
110
111
|
throw new Error('Requested tenant is outside the context boundary');
|
|
111
112
|
}
|
|
112
113
|
const seedRefs = (0, ref_js_1.normalizeContextRefs)(input.seedRefs);
|
|
113
|
-
assertSeedRefsAllowed(seedRefs, boundaryConnectors, hasConnectorBoundary);
|
|
114
|
-
}
|
|
115
|
-
|
|
114
|
+
assertSeedRefsAllowed(seedRefs, boundaryConnectors, hasConnectorBoundary, input.boundary.channels);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A seed ref must satisfy the same visibility rule as a row the reader would have returned.
|
|
118
|
+
*
|
|
119
|
+
* The connector check alone was never enough, and once raw visibility is decided by
|
|
120
|
+
* (connector, channel) it is plainly not: a caller could name the raw id of an event in an
|
|
121
|
+
* ungranted channel and have its identifiers - upstream source id and channel - come back
|
|
122
|
+
* in the packet's source_refs, which is then persisted. That is a citation path reaching
|
|
123
|
+
* past the reading path, the exact asymmetry this rebuild exists to remove.
|
|
124
|
+
*
|
|
125
|
+
* The channel is checked only when the boundary carries a grant. Without one there is no
|
|
126
|
+
* channel rule to enforce and the connector check stands, unchanged.
|
|
127
|
+
*/
|
|
128
|
+
function assertSeedRefsAllowed(seedRefs, boundaryConnectors, hasConnectorBoundary, channelGrant) {
|
|
116
129
|
for (const ref of seedRefs) {
|
|
117
|
-
if (ref.kind
|
|
130
|
+
if (ref.kind !== 'raw')
|
|
131
|
+
continue;
|
|
132
|
+
if (hasConnectorBoundary && !boundaryConnectors.has(ref.connector)) {
|
|
118
133
|
throw new Error(`Seed raw ref connector is outside the context boundary: ${ref.connector}`);
|
|
119
134
|
}
|
|
135
|
+
if (!channelGrant)
|
|
136
|
+
continue;
|
|
137
|
+
// Calls the shared rule rather than re-deriving membership. An inline re-derivation
|
|
138
|
+
// here already disagreed with it on the empty-channel case, which is how a fourth copy
|
|
139
|
+
// of a decision starts: not by being written as a copy, but by being written as
|
|
140
|
+
// "obviously the same check".
|
|
141
|
+
if (!(0, channel_grant_js_1.isChannelGranted)(ref.connector, ref.channel_id, channelGrant)) {
|
|
142
|
+
throw new Error(`Seed raw ref channel is outside the context boundary: ${ref.connector}`);
|
|
143
|
+
}
|
|
120
144
|
}
|
|
121
145
|
}
|
|
122
146
|
function isRecord(value) {
|
|
@@ -613,6 +613,10 @@ class NodeSQLiteAdapter extends base_adapter_js_1.DatabaseAdapter {
|
|
|
613
613
|
}
|
|
614
614
|
}
|
|
615
615
|
this.exec(`
|
|
616
|
+
CREATE INDEX IF NOT EXISTS idx_decisions_topic
|
|
617
|
+
ON decisions(topic)
|
|
618
|
+
`);
|
|
619
|
+
this.exec(`
|
|
616
620
|
CREATE INDEX IF NOT EXISTS idx_decisions_envelope_hash
|
|
617
621
|
ON decisions(envelope_hash)
|
|
618
622
|
`);
|
package/dist/db-manager.d.ts
CHANGED
|
@@ -147,11 +147,6 @@ export declare function getAdapter(): DatabaseAdapter;
|
|
|
147
147
|
*/
|
|
148
148
|
export declare function closeDB(): Promise<void>;
|
|
149
149
|
export declare function ensureMemoryScope(kind: string, externalId: string): Promise<string>;
|
|
150
|
-
export declare function bindMemoryToScope(memoryId: string, scopeId: string, isPrimary?: boolean): Promise<void>;
|
|
151
|
-
export declare function listScopesForMemory(memoryId: string): Promise<Array<{
|
|
152
|
-
kind: string;
|
|
153
|
-
id: string;
|
|
154
|
-
}>>;
|
|
155
150
|
/**
|
|
156
151
|
* Insert embedding into vector search table
|
|
157
152
|
*
|
|
@@ -301,12 +296,4 @@ export declare function fts5Search(query: string, limit?: number): Promise<{
|
|
|
301
296
|
id: string;
|
|
302
297
|
rank: number;
|
|
303
298
|
}[]>;
|
|
304
|
-
/**
|
|
305
|
-
* Re-generate all embeddings using the currently configured model.
|
|
306
|
-
* Call this after changing the embedding model in config.json.
|
|
307
|
-
*
|
|
308
|
-
* @param onProgress - Optional callback with (completed, total) counts
|
|
309
|
-
* @returns Number of embeddings regenerated
|
|
310
|
-
*/
|
|
311
|
-
export declare function reindexEmbeddings(onProgress?: (completed: number, total: number) => void): Promise<number>;
|
|
312
299
|
//# sourceMappingURL=db-manager.d.ts.map
|
package/dist/db-manager.js
CHANGED
|
@@ -29,8 +29,6 @@ exports.getDB = getDB;
|
|
|
29
29
|
exports.getAdapter = getAdapter;
|
|
30
30
|
exports.closeDB = closeDB;
|
|
31
31
|
exports.ensureMemoryScope = ensureMemoryScope;
|
|
32
|
-
exports.bindMemoryToScope = bindMemoryToScope;
|
|
33
|
-
exports.listScopesForMemory = listScopesForMemory;
|
|
34
32
|
exports.insertEmbedding = insertEmbedding;
|
|
35
33
|
exports.vectorSearch = vectorSearch;
|
|
36
34
|
exports.insertDecisionWithEmbedding = insertDecisionWithEmbedding;
|
|
@@ -43,7 +41,6 @@ exports.getDbPath = getDbPath;
|
|
|
43
41
|
exports.resetDBState = resetDBState;
|
|
44
42
|
exports.isTestMode = isTestMode;
|
|
45
43
|
exports.fts5Search = fts5Search;
|
|
46
|
-
exports.reindexEmbeddings = reindexEmbeddings;
|
|
47
44
|
const path_1 = __importDefault(require("path"));
|
|
48
45
|
const os_1 = __importDefault(require("os"));
|
|
49
46
|
const debug_logger_js_1 = require("./debug-logger.js");
|
|
@@ -262,27 +259,6 @@ async function ensureMemoryScope(kind, externalId) {
|
|
|
262
259
|
.run(id, kind, externalId);
|
|
263
260
|
return id;
|
|
264
261
|
}
|
|
265
|
-
async function bindMemoryToScope(memoryId, scopeId, isPrimary = false) {
|
|
266
|
-
const adapter = getAdapter();
|
|
267
|
-
adapter
|
|
268
|
-
.prepare(`
|
|
269
|
-
INSERT OR REPLACE INTO memory_scope_bindings (memory_id, scope_id, is_primary)
|
|
270
|
-
VALUES (?, ?, ?)
|
|
271
|
-
`)
|
|
272
|
-
.run(memoryId, scopeId, isPrimary ? 1 : 0);
|
|
273
|
-
}
|
|
274
|
-
async function listScopesForMemory(memoryId) {
|
|
275
|
-
const adapter = getAdapter();
|
|
276
|
-
return adapter
|
|
277
|
-
.prepare(`
|
|
278
|
-
SELECT ms.kind, ms.external_id AS id
|
|
279
|
-
FROM memory_scope_bindings msb
|
|
280
|
-
JOIN memory_scopes ms ON ms.id = msb.scope_id
|
|
281
|
-
WHERE msb.memory_id = ?
|
|
282
|
-
ORDER BY msb.is_primary DESC, ms.created_at ASC
|
|
283
|
-
`)
|
|
284
|
-
.all(memoryId);
|
|
285
|
-
}
|
|
286
262
|
/**
|
|
287
263
|
* Insert embedding into vector search table
|
|
288
264
|
*
|
|
@@ -811,46 +787,4 @@ async function fts5Search(query, limit = 10) {
|
|
|
811
787
|
`);
|
|
812
788
|
return stmt.all(query, limit);
|
|
813
789
|
}
|
|
814
|
-
/**
|
|
815
|
-
* Re-generate all embeddings using the currently configured model.
|
|
816
|
-
* Call this after changing the embedding model in config.json.
|
|
817
|
-
*
|
|
818
|
-
* @param onProgress - Optional callback with (completed, total) counts
|
|
819
|
-
* @returns Number of embeddings regenerated
|
|
820
|
-
*/
|
|
821
|
-
async function reindexEmbeddings(onProgress) {
|
|
822
|
-
const adapter = getAdapter();
|
|
823
|
-
const { generateEnhancedEmbedding } = await import('./embeddings.js');
|
|
824
|
-
const decisions = adapter
|
|
825
|
-
.prepare('SELECT rowid, topic, decision, reasoning, outcome, confidence FROM decisions')
|
|
826
|
-
.all();
|
|
827
|
-
if (typeof adapter.insertEmbedding !== 'function') {
|
|
828
|
-
throw new Error('Embedding insertion is not available on this adapter');
|
|
829
|
-
}
|
|
830
|
-
const total = decisions.length;
|
|
831
|
-
let completed = 0;
|
|
832
|
-
for (const row of decisions) {
|
|
833
|
-
try {
|
|
834
|
-
// e5 passage role (default) - stored vector
|
|
835
|
-
const embedding = await generateEnhancedEmbedding({
|
|
836
|
-
topic: row.topic,
|
|
837
|
-
decision: row.decision,
|
|
838
|
-
reasoning: row.reasoning || undefined,
|
|
839
|
-
outcome: row.outcome || undefined,
|
|
840
|
-
confidence: row.confidence ?? undefined,
|
|
841
|
-
});
|
|
842
|
-
adapter.insertEmbedding(row.rowid, embedding);
|
|
843
|
-
completed++;
|
|
844
|
-
if (onProgress && completed % 50 === 0) {
|
|
845
|
-
onProgress(completed, total);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
catch (e) {
|
|
849
|
-
(0, debug_logger_js_1.error)(`[db-manager] Failed to reindex rowid ${row.rowid}: ${e}`);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
onProgress?.(completed, total);
|
|
853
|
-
(0, debug_logger_js_1.info)(`[db-manager] Reindexed ${completed}/${total} embeddings`);
|
|
854
|
-
return completed;
|
|
855
|
-
}
|
|
856
790
|
//# sourceMappingURL=db-manager.js.map
|
|
@@ -140,13 +140,6 @@ export declare function ensureTokenBudget(text: string, maxTokens: number): stri
|
|
|
140
140
|
* Heuristic: ~1 token per 4 characters
|
|
141
141
|
*/
|
|
142
142
|
export declare function estimateTokens(text: string): number;
|
|
143
|
-
/**
|
|
144
|
-
* Format context in Claude-friendly Instant Answer format
|
|
145
|
-
*
|
|
146
|
-
* Story 014.7.10: Claude-Friendly Context Formatting
|
|
147
|
-
* AC #1: Instant Answer format with trust components
|
|
148
|
-
*/
|
|
149
|
-
export declare function formatInstantAnswer(decision: DecisionForFormat | null, options?: FormatOptions): string | null;
|
|
150
143
|
/**
|
|
151
144
|
* Extract quick answer from decision
|
|
152
145
|
*/
|
|
@@ -161,10 +154,6 @@ export declare function extractCodeExample(decision: DecisionForFormat): string
|
|
|
161
154
|
* Story 014.7.10 AC #2: Trust Context display
|
|
162
155
|
*/
|
|
163
156
|
export declare function formatTrustContext(trustCtx: TrustContext | null | undefined): string | null;
|
|
164
|
-
/**
|
|
165
|
-
* Format decision as curiosity-inducing teaser
|
|
166
|
-
*/
|
|
167
|
-
export declare function formatTeaser(decision: DecisionForFormat | null): string | null;
|
|
168
157
|
/**
|
|
169
158
|
* Format mama.recall() results in readable format
|
|
170
159
|
*/
|