claude-memory-layer 1.0.45 → 1.0.47
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 +4 -2
- package/dist/cli/index.js +126 -12
- package/dist/cli/index.js.map +2 -2
- package/dist/core/index.js +1209 -24
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +125 -11
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/semantic-daemon.js +125 -11
- package/dist/hooks/semantic-daemon.js.map +2 -2
- package/dist/hooks/session-end.js +125 -11
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +125 -11
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +125 -11
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +125 -11
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/index.js +1215 -30
- package/dist/index.js.map +4 -4
- package/dist/mcp/index.js +305 -45
- package/dist/mcp/index.js.map +2 -2
- package/dist/server/api/index.js +125 -11
- package/dist/server/api/index.js.map +2 -2
- package/dist/server/index.js +125 -11
- package/dist/server/index.js.map +2 -2
- package/dist/services/memory-service.js +125 -11
- package/dist/services/memory-service.js.map +2 -2
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -2622,6 +2622,7 @@ var VectorOutbox = class {
|
|
|
2622
2622
|
// src/core/retrieval-debug-lanes.ts
|
|
2623
2623
|
var RETRIEVAL_DEBUG_LANE_NAMES = [
|
|
2624
2624
|
"raw_event",
|
|
2625
|
+
"session_event",
|
|
2625
2626
|
"session_summary",
|
|
2626
2627
|
"graph_path",
|
|
2627
2628
|
"facet_match"
|
|
@@ -10662,6 +10663,18 @@ var COMMAND_ARTIFACT_PATTERNS = [
|
|
|
10662
10663
|
/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/i,
|
|
10663
10664
|
/<local-command-stderr>[\s\S]*?<\/local-command-stderr>/i
|
|
10664
10665
|
];
|
|
10666
|
+
var LOW_SIGNAL_CONTEXT_PATTERNS = [
|
|
10667
|
+
/<environment_context\b[\s\S]*<\/environment_context>/i,
|
|
10668
|
+
/<turn_aborted>/i,
|
|
10669
|
+
/^#\s*AGENTS\.md\s+instructions\b[\s\S]*<INSTRUCTIONS>/i,
|
|
10670
|
+
/^\s*(?:understood[,\s.]*)?(?:stopping|stopped|pausing|paused)\s+here\b[\s\S]{0,180}\blet\s+me\s+know\s+when\s+you(?:'d|\s+would)?\s+like\s+to\s+continue\b/i,
|
|
10671
|
+
/^\s*\[?CONTEXT\s+COMPACTION\s*[—-]\s*REFERENCE\s+ONLY\]?\b[\s\S]{0,600}\b(?:earlier\s+turns\s+were\s+compacted|handoff\s+from\s+a\s+previous\s+context\s+window|active\s+task)\b/i,
|
|
10672
|
+
/^\s*Summary\s+generation\s+was\s+unavailable\.\s*\d+\s+message\(s\)\s+were\s+removed\s+to\s+free\s+context\s+space\b/i,
|
|
10673
|
+
/^\s*---\s*END\s+OF\s+CONTEXT\s+SUMMARY\b/i,
|
|
10674
|
+
/^\s*\[Your\s+active\s+task\s+list\s+was\s+preserved\s+across\s+context\s+compression\]/i,
|
|
10675
|
+
/^➜\s+\S+\s+git:\([^)]*\)\s+/i,
|
|
10676
|
+
/^\$\s+\S+/i
|
|
10677
|
+
];
|
|
10665
10678
|
var CONTINUATION_QUERY_PATTERNS = [
|
|
10666
10679
|
/^\s*(?:continue|resume|next|what(?:'s| is)? next|next\s+(?:step|task|action)|recommended\s+(?:next\s+)?(?:step|task|action)|what should (?:we|i) do next)\??\s*$/i,
|
|
10667
10680
|
/^\s*(?:응\s*)?(?:이어서(?:\s*진행(?:해줘)?)?|계속(?:\s*해줘)?|다음\s*(?:단계|작업|추천\s*작업|추천|할\s*일)?(?:은|는)?(?:\s*(?:뭐야|진행(?:해줘)?))?\??|남은\s*(?:추가(?:로)?\s*)?(?:(?:할\s*만한\s*)?(?:작업|일)|할\s*일)?(?:은|는)?\s*(?:있어|있나|있나요|뭐야)\??|추천\s*작업(?:은|는)?(?:\s*뭐야)?\??|진행해줘)\s*$/i
|
|
@@ -10673,7 +10686,7 @@ var SHORT_REPAIR_FOLLOW_UP_PATTERNS = [
|
|
|
10673
10686
|
var CURRENT_STATE_QUERY_PATTERNS = [
|
|
10674
10687
|
/\bcurrent\b.*\b(?:state|status|deployment|blocker|pr|pull request)\b/i,
|
|
10675
10688
|
/\b(?:still|as current|current)\b.*\b(?:unresolved|open|pending|not completed)\b/i,
|
|
10676
|
-
/\b(?:old|obsolete|stale|resolved|already resolved)\b.*\b(?:current|still|unresolved|open|
|
|
10689
|
+
/\b(?:old|obsolete|stale|resolved|already resolved)\b.*\b(?:current|still|unresolved|open|status)\b/i,
|
|
10677
10690
|
/(?:현재|아직|이전|오래된|해결된).*(?:상태|미해결|열린|블로커|PR|풀리퀘스트)/i
|
|
10678
10691
|
];
|
|
10679
10692
|
var STALE_CONTENT_PATTERNS = [
|
|
@@ -10819,6 +10832,21 @@ function isCommandArtifactQuery(query) {
|
|
|
10819
10832
|
if (normalized.includes("command-name") || normalized.includes("command-message")) return true;
|
|
10820
10833
|
return COMMAND_ARTIFACT_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
10821
10834
|
}
|
|
10835
|
+
function isCommandArtifactContent(content) {
|
|
10836
|
+
const trimmed = content.trim();
|
|
10837
|
+
if (!trimmed) return false;
|
|
10838
|
+
const normalized = trimmed.toLowerCase();
|
|
10839
|
+
if (normalized.includes("local-command-stdout") || normalized.includes("local-command-stderr")) return true;
|
|
10840
|
+
if (normalized.includes("command-name") || normalized.includes("command-message")) return true;
|
|
10841
|
+
return COMMAND_ARTIFACT_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
10842
|
+
}
|
|
10843
|
+
function isLowSignalContextContent(content) {
|
|
10844
|
+
const trimmed = content.trim();
|
|
10845
|
+
if (!trimmed) return true;
|
|
10846
|
+
if (isCommandArtifactContent(trimmed)) return true;
|
|
10847
|
+
if (LOW_SIGNAL_CONTEXT_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
|
|
10848
|
+
return false;
|
|
10849
|
+
}
|
|
10822
10850
|
function isGenericContinuationQuery(query) {
|
|
10823
10851
|
const trimmed = query.trim();
|
|
10824
10852
|
if (!trimmed) return false;
|
|
@@ -10836,6 +10864,16 @@ function isShortRepairFollowUpQuery(query) {
|
|
|
10836
10864
|
if (tokens.length > 8) return false;
|
|
10837
10865
|
return SHORT_REPAIR_FOLLOW_UP_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
10838
10866
|
}
|
|
10867
|
+
function isLowConfidenceContextFallbackQuery(query) {
|
|
10868
|
+
const trimmed = query.trim();
|
|
10869
|
+
if (!trimmed) return false;
|
|
10870
|
+
if (isGenericContinuationQuery(trimmed) || isShortRepairFollowUpQuery(trimmed)) return true;
|
|
10871
|
+
const terms = new Set(tokenizeQualityText(trimmed));
|
|
10872
|
+
if ((terms.has("compacted") || terms.has("compaction")) && terms.has("handoff")) return false;
|
|
10873
|
+
const hasContinuationRecall = /^(?:continue|resume)\b/i.test(trimmed) && (terms.has("work") || terms.has("step") || terms.has("task") || terms.has("last") || terms.has("completed"));
|
|
10874
|
+
const hasValidationGateRecall = terms.has("validation") && (terms.has("gate") || terms.has("check")) && (terms.has("run") || terms.has("before") || terms.has("commit") || terms.has("committing") || terms.has("change"));
|
|
10875
|
+
return hasContinuationRecall || hasValidationGateRecall;
|
|
10876
|
+
}
|
|
10839
10877
|
function isCurrentStateQuery(query) {
|
|
10840
10878
|
const trimmed = query.trim();
|
|
10841
10879
|
if (!trimmed) return false;
|
|
@@ -11009,6 +11047,7 @@ var Retriever = class {
|
|
|
11009
11047
|
}
|
|
11010
11048
|
async retrieve(query, options = {}) {
|
|
11011
11049
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
11050
|
+
const retrievalMode = options.retrievalMode ?? ((options.strategy ?? DEFAULT_OPTIONS.strategy) === "auto" ? "session-event-hybrid" : "event");
|
|
11012
11051
|
const sessionFilter = opts.scope?.sessionId ?? opts.sessionId;
|
|
11013
11052
|
const fallbackTrace = [];
|
|
11014
11053
|
const qualityQuery = buildRetrievalQualityQuery(query);
|
|
@@ -11039,6 +11078,7 @@ var Retriever = class {
|
|
|
11039
11078
|
decayPolicy: opts.decayPolicy,
|
|
11040
11079
|
intentRewrite: opts.intentRewrite === true,
|
|
11041
11080
|
graphHop: opts.graphHop,
|
|
11081
|
+
retrievalMode,
|
|
11042
11082
|
projectScopeMode: opts.projectScopeMode,
|
|
11043
11083
|
projectHash: opts.projectHash,
|
|
11044
11084
|
allowedProjectHashes: opts.allowedProjectHashes,
|
|
@@ -11057,6 +11097,7 @@ var Retriever = class {
|
|
|
11057
11097
|
rerankWeights: opts.rerankWeights,
|
|
11058
11098
|
decayPolicy: opts.decayPolicy,
|
|
11059
11099
|
graphHop: opts.graphHop,
|
|
11100
|
+
retrievalMode,
|
|
11060
11101
|
projectScopeMode: opts.projectScopeMode,
|
|
11061
11102
|
projectHash: opts.projectHash,
|
|
11062
11103
|
allowedProjectHashes: opts.allowedProjectHashes,
|
|
@@ -11076,6 +11117,7 @@ var Retriever = class {
|
|
|
11076
11117
|
rerankWeights: opts.rerankWeights,
|
|
11077
11118
|
decayPolicy: opts.decayPolicy,
|
|
11078
11119
|
graphHop: opts.graphHop,
|
|
11120
|
+
retrievalMode,
|
|
11079
11121
|
projectScopeMode: opts.projectScopeMode,
|
|
11080
11122
|
projectHash: opts.projectHash,
|
|
11081
11123
|
allowedProjectHashes: opts.allowedProjectHashes,
|
|
@@ -11096,14 +11138,37 @@ var Retriever = class {
|
|
|
11096
11138
|
query,
|
|
11097
11139
|
minScore: opts.minScore
|
|
11098
11140
|
});
|
|
11141
|
+
const expandedSummary = retrievalMode === "session-event-hybrid" ? await this.expandSessionEventHybrid(filteredSummary, {
|
|
11142
|
+
query: qualityQuery,
|
|
11143
|
+
currentStateQuery: query,
|
|
11144
|
+
limit: opts.topK * 4
|
|
11145
|
+
}) : filteredSummary;
|
|
11146
|
+
const scopedExpandedSummary = retrievalMode === "session-event-hybrid" ? await this.applyScopeFilters(expandedSummary, {
|
|
11147
|
+
scope: opts.scope,
|
|
11148
|
+
projectScopeMode: opts.projectScopeMode,
|
|
11149
|
+
projectHash: opts.projectHash,
|
|
11150
|
+
allowedProjectHashes: opts.allowedProjectHashes,
|
|
11151
|
+
facets: opts.facets
|
|
11152
|
+
}) : expandedSummary;
|
|
11153
|
+
const finalSummary = retrievalMode === "session-event-hybrid" ? this.applyQualityFilters(scopedExpandedSummary, {
|
|
11154
|
+
query,
|
|
11155
|
+
minScore: opts.minScore
|
|
11156
|
+
}) : scopedExpandedSummary;
|
|
11099
11157
|
current = {
|
|
11100
|
-
results:
|
|
11101
|
-
candidateResults:
|
|
11102
|
-
matchResult: this.matcher.matchSearchResults(
|
|
11158
|
+
results: finalSummary,
|
|
11159
|
+
candidateResults: finalSummary,
|
|
11160
|
+
matchResult: this.matcher.matchSearchResults(finalSummary, () => 0)
|
|
11103
11161
|
};
|
|
11104
11162
|
fallbackTrace.push("fallback:summary");
|
|
11105
11163
|
}
|
|
11106
|
-
const
|
|
11164
|
+
const selectedResults = current.results.slice(0, opts.topK).filter((result2) => {
|
|
11165
|
+
if (current.matchResult.confidence !== "none") return true;
|
|
11166
|
+
if (isLowConfidenceContextFallbackQuery(query)) {
|
|
11167
|
+
return (result2.semanticScore ?? result2.score) >= 0.5 || result2.score >= 0.5;
|
|
11168
|
+
}
|
|
11169
|
+
return (result2.semanticScore ?? result2.score) >= 0.62 || result2.score >= 0.62;
|
|
11170
|
+
});
|
|
11171
|
+
const memories = await this.enrichResults(selectedResults, opts, query);
|
|
11107
11172
|
const context = this.buildContext(memories, opts.maxTokens);
|
|
11108
11173
|
return {
|
|
11109
11174
|
memories,
|
|
@@ -11111,7 +11176,7 @@ var Retriever = class {
|
|
|
11111
11176
|
totalTokens: this.estimateTokens(context),
|
|
11112
11177
|
context,
|
|
11113
11178
|
fallbackTrace,
|
|
11114
|
-
selectedDebug:
|
|
11179
|
+
selectedDebug: selectedResults.map((r) => this.debugDetailForResult(r)),
|
|
11115
11180
|
candidateDebug: (current.candidateResults || []).slice(0, Math.max(opts.topK * 3, 20)).map((r) => this.debugDetailForResult(r)),
|
|
11116
11181
|
rawQueryText: current.queryRewriteKind ? query : void 0,
|
|
11117
11182
|
effectiveQueryText: current.effectiveQueryText,
|
|
@@ -11178,13 +11243,18 @@ var Retriever = class {
|
|
|
11178
11243
|
initialResults = this.mergeResults(initialResults, rewrittenResults, input.topK * 3);
|
|
11179
11244
|
}
|
|
11180
11245
|
}
|
|
11181
|
-
const
|
|
11246
|
+
const graphExpandedResults = input.graphHop?.enabled === false ? initialResults : await this.expandGraphHops(initialResults, {
|
|
11182
11247
|
query,
|
|
11183
11248
|
queryGraphEnabled: this.queryGraphExpansionEnabled,
|
|
11184
11249
|
maxHops: clampGraphHops(input.graphHop?.maxHops ?? 1),
|
|
11185
11250
|
hopPenalty: Math.max(0, input.graphHop?.hopPenalty ?? 0.08),
|
|
11186
11251
|
limit: input.topK * 4
|
|
11187
11252
|
});
|
|
11253
|
+
const expandedResults = input.retrievalMode === "session-event-hybrid" ? await this.expandSessionEventHybrid(graphExpandedResults, {
|
|
11254
|
+
query: rerankQuery,
|
|
11255
|
+
currentStateQuery: query,
|
|
11256
|
+
limit: input.topK * 4
|
|
11257
|
+
}) : graphExpandedResults;
|
|
11188
11258
|
const rerankedResults = input.rerankWithKeyword ? this.rerankByKeywordOverlap(expandedResults, rerankQuery, input.rerankWeights, input.decayPolicy) : expandedResults;
|
|
11189
11259
|
const filtered = await this.applyScopeFilters(rerankedResults, {
|
|
11190
11260
|
scope: input.scope,
|
|
@@ -11206,6 +11276,7 @@ var Retriever = class {
|
|
|
11206
11276
|
if (isCurrentStateQuery(options.query)) {
|
|
11207
11277
|
filtered = filtered.filter((result2) => !isStaleOrSupersededContent(result2.content));
|
|
11208
11278
|
}
|
|
11279
|
+
filtered = filtered.filter((result2) => !isLowSignalContextContent(result2.content));
|
|
11209
11280
|
filtered = filtered.filter(
|
|
11210
11281
|
(result2) => this.isGraphPathResult(result2) || hasDiscriminativeTermOverlap(options.query, result2.content)
|
|
11211
11282
|
);
|
|
@@ -11231,6 +11302,47 @@ var Retriever = class {
|
|
|
11231
11302
|
}
|
|
11232
11303
|
return [...byId.values()].sort((a, b) => b.score - a.score).slice(0, limit);
|
|
11233
11304
|
}
|
|
11305
|
+
async expandSessionEventHybrid(seeds, opts) {
|
|
11306
|
+
if (seeds.length === 0 || opts.limit <= seeds.length) return seeds;
|
|
11307
|
+
const queryTokens = this.tokenize(opts.query);
|
|
11308
|
+
if (queryTokens.length === 0) return seeds;
|
|
11309
|
+
const byId = /* @__PURE__ */ new Map();
|
|
11310
|
+
for (const seed of seeds) byId.set(seed.eventId, seed);
|
|
11311
|
+
const bestSeedBySession = /* @__PURE__ */ new Map();
|
|
11312
|
+
for (const seed of [...seeds].sort((a, b) => b.score - a.score || compareStable(a.eventId, b.eventId))) {
|
|
11313
|
+
if (!seed.sessionId || bestSeedBySession.has(seed.sessionId)) continue;
|
|
11314
|
+
bestSeedBySession.set(seed.sessionId, seed);
|
|
11315
|
+
}
|
|
11316
|
+
const suppressStaleState = isCurrentStateQuery(opts.currentStateQuery);
|
|
11317
|
+
for (const [sessionId, seed] of bestSeedBySession) {
|
|
11318
|
+
const sessionEvents = await this.eventStore.getSessionEvents(sessionId);
|
|
11319
|
+
for (const event of [...sessionEvents].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime())) {
|
|
11320
|
+
if (byId.has(event.id)) continue;
|
|
11321
|
+
if (isLowSignalContextContent(event.content)) continue;
|
|
11322
|
+
if (suppressStaleState && isStaleOrSupersededContent(event.content)) continue;
|
|
11323
|
+
const lexicalScore = this.keywordOverlap(queryTokens, this.tokenize(event.content));
|
|
11324
|
+
if (lexicalScore <= 0) continue;
|
|
11325
|
+
if (shouldApplyTechnicalGuard(opts.query) && !hasTechnicalTermOverlap(opts.query, event.content)) continue;
|
|
11326
|
+
const score = Math.min(0.95, Math.max(0.35, seed.score * 0.72 + lexicalScore * 0.28));
|
|
11327
|
+
const row = withRetrievalLane({
|
|
11328
|
+
id: `session-event-${seed.eventId}-${event.id}`,
|
|
11329
|
+
eventId: event.id,
|
|
11330
|
+
content: event.content,
|
|
11331
|
+
score,
|
|
11332
|
+
sessionId: event.sessionId,
|
|
11333
|
+
eventType: event.eventType,
|
|
11334
|
+
timestamp: event.timestamp.toISOString(),
|
|
11335
|
+
semanticScore: seed.semanticScore ?? seed.score,
|
|
11336
|
+
lexicalScore,
|
|
11337
|
+
recencyScore: seed.recencyScore
|
|
11338
|
+
}, { lane: "session_event", reason: `same_session:${seed.eventId}`, score });
|
|
11339
|
+
byId.set(row.eventId, row);
|
|
11340
|
+
if (byId.size >= opts.limit) break;
|
|
11341
|
+
}
|
|
11342
|
+
if (byId.size >= opts.limit) break;
|
|
11343
|
+
}
|
|
11344
|
+
return [...byId.values()].sort((a, b) => b.score - a.score || compareStable(a.eventId, b.eventId)).slice(0, opts.limit);
|
|
11345
|
+
}
|
|
11234
11346
|
async expandGraphHops(seeds, opts) {
|
|
11235
11347
|
const byId = /* @__PURE__ */ new Map();
|
|
11236
11348
|
for (const s of seeds) byId.set(s.eventId, s);
|
|
@@ -11546,7 +11658,7 @@ var Retriever = class {
|
|
|
11546
11658
|
async retrieveRecent(limit = 100) {
|
|
11547
11659
|
return this.eventStore.getRecentEvents(limit);
|
|
11548
11660
|
}
|
|
11549
|
-
async enrichResults(results, options) {
|
|
11661
|
+
async enrichResults(results, options, query) {
|
|
11550
11662
|
const memories = [];
|
|
11551
11663
|
for (const result2 of results) {
|
|
11552
11664
|
const event = await this.eventStore.getEvent(result2.eventId);
|
|
@@ -11556,13 +11668,13 @@ var Retriever = class {
|
|
|
11556
11668
|
}
|
|
11557
11669
|
let sessionContext;
|
|
11558
11670
|
if (options.includeSessionContext) {
|
|
11559
|
-
sessionContext = await this.getSessionContext(event.sessionId, event.id);
|
|
11671
|
+
sessionContext = await this.getSessionContext(event.sessionId, event.id, query);
|
|
11560
11672
|
}
|
|
11561
11673
|
memories.push({ event, score: result2.score, sessionContext });
|
|
11562
11674
|
}
|
|
11563
11675
|
return memories;
|
|
11564
11676
|
}
|
|
11565
|
-
async getSessionContext(sessionId, eventId) {
|
|
11677
|
+
async getSessionContext(sessionId, eventId, query) {
|
|
11566
11678
|
const sessionEvents = await this.eventStore.getSessionEvents(sessionId);
|
|
11567
11679
|
const eventIndex = sessionEvents.findIndex((e) => e.id === eventId);
|
|
11568
11680
|
if (eventIndex === -1) return void 0;
|
|
@@ -11570,7 +11682,9 @@ var Retriever = class {
|
|
|
11570
11682
|
const end = Math.min(sessionEvents.length, eventIndex + 2);
|
|
11571
11683
|
const contextEvents = sessionEvents.slice(start, end);
|
|
11572
11684
|
if (contextEvents.length <= 1) return void 0;
|
|
11573
|
-
|
|
11685
|
+
const suppressStaleState = isCurrentStateQuery(query);
|
|
11686
|
+
const contextLines = contextEvents.filter((e) => e.id !== eventId).filter((e) => !isLowSignalContextContent(e.content)).filter((e) => !(suppressStaleState && isStaleOrSupersededContent(e.content))).map((e) => `[${e.eventType}]: ${e.content.slice(0, 200)}...`);
|
|
11687
|
+
return contextLines.length > 0 ? contextLines.join("\n") : void 0;
|
|
11574
11688
|
}
|
|
11575
11689
|
buildUnifiedContext(projectResult, sharedMemories) {
|
|
11576
11690
|
let context = projectResult.context;
|
|
@@ -17118,12 +17232,13 @@ var productValidationMatrix = [
|
|
|
17118
17232
|
requirements: [
|
|
17119
17233
|
"Expose an agent-ready project context pack that combines relevant retrieval results with recent project timeline.",
|
|
17120
17234
|
"Support projectPath scoping so Hermes, Codex, and Claude Code can share the same project memory backend.",
|
|
17121
|
-
"Keep output compact and citation-oriented so agents can follow up with source-ref or timeline tools."
|
|
17235
|
+
"Keep output compact and citation-oriented so agents can follow up with source-ref or timeline tools.",
|
|
17236
|
+
"Document MCP-only context-pack budget controls (`maxChars`, approximate `maxTokens`, and `compression: off|safe|aggressive`) and make the CLI/API parity boundary explicit."
|
|
17122
17237
|
],
|
|
17123
17238
|
evidence: [
|
|
17124
|
-
{ kind: "test", ref: "tests/extensions/mcp-context-tools.test.ts", note: "Asserts context-pack output, projectPath routing, compact relevant memory citations, and recent timeline inclusion." },
|
|
17125
|
-
{ kind: "source", ref: "src/extensions/mcp/handlers.ts", note: "mem-context-pack handler formats relevant memories plus session summaries." },
|
|
17126
|
-
{ kind: "source", ref: "src/extensions/mcp/tools.ts", note: "MCP tool schema advertises projectPath, topK, recentLimit, and
|
|
17239
|
+
{ kind: "test", ref: "tests/extensions/mcp-context-tools.test.ts", note: "Asserts context-pack output, projectPath routing, compact relevant memory citations, sourceRef preservation, maxChars/maxTokens budget enforcement, compression modes, and recent timeline inclusion." },
|
|
17240
|
+
{ kind: "source", ref: "src/extensions/mcp/handlers.ts", note: "mem-context-pack handler formats relevant memories plus session summaries and validates MCP-only budget/compression arguments before storage access." },
|
|
17241
|
+
{ kind: "source", ref: "src/extensions/mcp/tools.ts", note: "MCP tool schema advertises projectPath, topK, recentLimit, sessionLimit, maxChars, maxTokens, and compression options; CLI/API parity is documented as MCP-only for context-pack generation." }
|
|
17127
17242
|
]
|
|
17128
17243
|
},
|
|
17129
17244
|
{
|
|
@@ -17238,6 +17353,985 @@ function renderProductValidationMatrixMarkdown(matrix = productValidationMatrix)
|
|
|
17238
17353
|
`;
|
|
17239
17354
|
}
|
|
17240
17355
|
|
|
17356
|
+
// src/core/source/source-schema.ts
|
|
17357
|
+
var SOURCE_PRIVACY_CLASSES = Object.freeze(["public", "internal", "confidential", "restricted"]);
|
|
17358
|
+
var SOURCE_CAPTURE_MODES = Object.freeze(["snapshot", "append-only-log", "stream", "metadata-only", "history_import"]);
|
|
17359
|
+
var SourceContractValidationError = class extends Error {
|
|
17360
|
+
violations;
|
|
17361
|
+
constructor(violations) {
|
|
17362
|
+
super(`Source contract validation failed: ${violations.map((violation2) => violation2.code).join(", ")}`);
|
|
17363
|
+
this.name = "SourceContractValidationError";
|
|
17364
|
+
this.violations = violations;
|
|
17365
|
+
}
|
|
17366
|
+
};
|
|
17367
|
+
function isSourcePrivacyClass(value) {
|
|
17368
|
+
return isOneOf(SOURCE_PRIVACY_CLASSES, value);
|
|
17369
|
+
}
|
|
17370
|
+
function isSourceCaptureMode(value) {
|
|
17371
|
+
return isOneOf(SOURCE_CAPTURE_MODES, value);
|
|
17372
|
+
}
|
|
17373
|
+
function defineSourceSchema(schema) {
|
|
17374
|
+
const prepared = prepareSourceSchema(schema);
|
|
17375
|
+
if (prepared.violations.length > 0 || !prepared.record) {
|
|
17376
|
+
throw new SourceContractValidationError(prepared.violations);
|
|
17377
|
+
}
|
|
17378
|
+
return Object.freeze(freezeSourceSchemaDeclaration(prepared.record));
|
|
17379
|
+
}
|
|
17380
|
+
function validateSourceSchema(schema, path12 = "source") {
|
|
17381
|
+
return prepareSourceSchema(schema, path12).violations;
|
|
17382
|
+
}
|
|
17383
|
+
function prepareSourceSchema(schema, path12 = "source") {
|
|
17384
|
+
const snapshot = snapshotAllowedRecordFields(schema, ["id", "version", "privacyClass", "captureMode", "description", "metadataSchema"], {
|
|
17385
|
+
path: path12,
|
|
17386
|
+
requiredCode: "source.required",
|
|
17387
|
+
requiredMessage: "Source schema declaration is required.",
|
|
17388
|
+
unknownCode: "source.unknown_field",
|
|
17389
|
+
unknownMessage: "Source schema declaration contains unsupported fields.",
|
|
17390
|
+
accessorCode: "source.accessor_field",
|
|
17391
|
+
accessorMessage: "Source schema declaration fields must be data properties."
|
|
17392
|
+
});
|
|
17393
|
+
if (!snapshot.record) return snapshot;
|
|
17394
|
+
return {
|
|
17395
|
+
record: snapshot.record,
|
|
17396
|
+
violations: validateSourceSchemaRecord(snapshot.record, path12, snapshot.violations)
|
|
17397
|
+
};
|
|
17398
|
+
}
|
|
17399
|
+
function validateSourceSchemaRecord(schema, path12, initialViolations) {
|
|
17400
|
+
const violations = [...initialViolations];
|
|
17401
|
+
const id = getOwnField(schema, "id");
|
|
17402
|
+
const version = getOwnField(schema, "version");
|
|
17403
|
+
const privacyClass = getOwnField(schema, "privacyClass");
|
|
17404
|
+
const captureMode = getOwnField(schema, "captureMode");
|
|
17405
|
+
const description = getOwnField(schema, "description");
|
|
17406
|
+
const metadataSchema = getOwnField(schema, "metadataSchema");
|
|
17407
|
+
if (!hasText(id)) {
|
|
17408
|
+
violations.push(violation("source.id.required", `${path12}.id`, "Source schema id must be non-empty."));
|
|
17409
|
+
} else {
|
|
17410
|
+
if (!isStableContractIdentifier(id)) {
|
|
17411
|
+
violations.push(violation("source.id.unstable", `${path12}.id`, "Source schema id must be stable and must not include a local absolute path."));
|
|
17412
|
+
}
|
|
17413
|
+
if (looksLikePrivacySensitiveSourceValue(id)) {
|
|
17414
|
+
violations.push(violation("source.id.privacy_sensitive", `${path12}.id`, "Source schema id must not leak local state handles or credential-shaped values."));
|
|
17415
|
+
}
|
|
17416
|
+
}
|
|
17417
|
+
if (!hasText(version)) {
|
|
17418
|
+
violations.push(violation("source.version.required", `${path12}.version`, "Source schema version must be non-empty."));
|
|
17419
|
+
} else if (looksLikePrivacySensitiveSourceValue(version)) {
|
|
17420
|
+
violations.push(violation("source.version.privacy_sensitive", `${path12}.version`, "Source schema version must not leak local state handles or credential-shaped values."));
|
|
17421
|
+
}
|
|
17422
|
+
if (!isSourcePrivacyClass(privacyClass)) {
|
|
17423
|
+
violations.push(violation("source.privacyClass.invalid", `${path12}.privacyClass`, "Source schema privacyClass must be one of the bounded source privacy classes."));
|
|
17424
|
+
}
|
|
17425
|
+
if (!isSourceCaptureMode(captureMode)) {
|
|
17426
|
+
violations.push(violation("source.captureMode.invalid", `${path12}.captureMode`, "Source schema captureMode must be one of the bounded source capture modes."));
|
|
17427
|
+
}
|
|
17428
|
+
if (description !== void 0 && typeof description !== "string") {
|
|
17429
|
+
violations.push(violation("source.description.invalid", `${path12}.description`, "Source schema description must be a string when present."));
|
|
17430
|
+
} else if (hasText(description) && looksLikePrivacySensitiveSourceValue(description)) {
|
|
17431
|
+
violations.push(violation("source.description.privacy_sensitive", `${path12}.description`, "Source schema description must not leak local state handles or credential-shaped values."));
|
|
17432
|
+
}
|
|
17433
|
+
if (metadataSchema !== void 0 && typeof metadataSchema !== "string") {
|
|
17434
|
+
violations.push(violation("source.metadataSchema.invalid", `${path12}.metadataSchema`, "Source schema metadataSchema must be a string when present."));
|
|
17435
|
+
} else if (hasText(metadataSchema) && looksLikePrivacySensitiveSourceValue(metadataSchema)) {
|
|
17436
|
+
violations.push(violation("source.metadataSchema.privacy_sensitive", `${path12}.metadataSchema`, "Source schema metadataSchema must not leak local state handles or credential-shaped values."));
|
|
17437
|
+
}
|
|
17438
|
+
return violations;
|
|
17439
|
+
}
|
|
17440
|
+
function freezeSourceSchemaDeclaration(schema) {
|
|
17441
|
+
const id = getOwnField(schema, "id");
|
|
17442
|
+
const version = getOwnField(schema, "version");
|
|
17443
|
+
const privacyClass = getOwnField(schema, "privacyClass");
|
|
17444
|
+
const captureMode = getOwnField(schema, "captureMode");
|
|
17445
|
+
const ownDescription = getOwnField(schema, "description");
|
|
17446
|
+
const ownMetadataSchema = getOwnField(schema, "metadataSchema");
|
|
17447
|
+
const defined = {
|
|
17448
|
+
id,
|
|
17449
|
+
version,
|
|
17450
|
+
privacyClass,
|
|
17451
|
+
captureMode
|
|
17452
|
+
};
|
|
17453
|
+
if (ownDescription !== void 0) defined.description = ownDescription;
|
|
17454
|
+
if (ownMetadataSchema !== void 0) defined.metadataSchema = ownMetadataSchema;
|
|
17455
|
+
return defined;
|
|
17456
|
+
}
|
|
17457
|
+
function hasText(value) {
|
|
17458
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
17459
|
+
}
|
|
17460
|
+
function isArrayForSourceSnapshot(value) {
|
|
17461
|
+
try {
|
|
17462
|
+
return Array.isArray(value);
|
|
17463
|
+
} catch {
|
|
17464
|
+
return false;
|
|
17465
|
+
}
|
|
17466
|
+
}
|
|
17467
|
+
function isRecord6(value) {
|
|
17468
|
+
return typeof value === "object" && value !== null && !isArrayForSourceSnapshot(value);
|
|
17469
|
+
}
|
|
17470
|
+
function hasOwnField(record, key) {
|
|
17471
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
17472
|
+
}
|
|
17473
|
+
function getOwnField(record, key) {
|
|
17474
|
+
try {
|
|
17475
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
17476
|
+
return descriptor && "value" in descriptor ? descriptor.value : void 0;
|
|
17477
|
+
} catch {
|
|
17478
|
+
return void 0;
|
|
17479
|
+
}
|
|
17480
|
+
}
|
|
17481
|
+
function safeOwnKeysForSourceSnapshot(value, path12, code, message) {
|
|
17482
|
+
try {
|
|
17483
|
+
return { keys: Reflect.ownKeys(value), violations: [] };
|
|
17484
|
+
} catch {
|
|
17485
|
+
return { keys: [], violations: [violation(code, path12, message)] };
|
|
17486
|
+
}
|
|
17487
|
+
}
|
|
17488
|
+
function safeGetOwnPropertyDescriptorForSourceSnapshot(value, key, path12, code, message) {
|
|
17489
|
+
try {
|
|
17490
|
+
return { descriptor: Object.getOwnPropertyDescriptor(value, key), violations: [] };
|
|
17491
|
+
} catch {
|
|
17492
|
+
return { violations: [violation(code, path12, message)] };
|
|
17493
|
+
}
|
|
17494
|
+
}
|
|
17495
|
+
function safeReadOwnDataPropertyForSourceSnapshot(value, key, descriptor, path12, code, message) {
|
|
17496
|
+
if (!("value" in descriptor)) {
|
|
17497
|
+
return { violations: [violation(code, path12, message)] };
|
|
17498
|
+
}
|
|
17499
|
+
try {
|
|
17500
|
+
const readValue = Reflect.get(value, key, value);
|
|
17501
|
+
if (!Object.is(readValue, descriptor.value)) {
|
|
17502
|
+
return { violations: [violation(code, path12, message)] };
|
|
17503
|
+
}
|
|
17504
|
+
return { value: descriptor.value, violations: [] };
|
|
17505
|
+
} catch {
|
|
17506
|
+
return { violations: [violation(code, path12, message)] };
|
|
17507
|
+
}
|
|
17508
|
+
}
|
|
17509
|
+
function snapshotAllowedRecordFields(value, allowedFields, options) {
|
|
17510
|
+
if (!isRecord6(value)) {
|
|
17511
|
+
return {
|
|
17512
|
+
record: void 0,
|
|
17513
|
+
violations: [violation(options.requiredCode, options.path, options.requiredMessage)]
|
|
17514
|
+
};
|
|
17515
|
+
}
|
|
17516
|
+
const allowed = new Set(allowedFields);
|
|
17517
|
+
const record = /* @__PURE__ */ Object.create(null);
|
|
17518
|
+
const keySnapshot = safeOwnKeysForSourceSnapshot(value, options.path, options.accessorCode, options.accessorMessage);
|
|
17519
|
+
const violations = [...keySnapshot.violations];
|
|
17520
|
+
for (const key of keySnapshot.keys) {
|
|
17521
|
+
if (typeof key !== "string" || !allowed.has(key)) {
|
|
17522
|
+
violations.push(violation(options.unknownCode, options.path, options.unknownMessage));
|
|
17523
|
+
continue;
|
|
17524
|
+
}
|
|
17525
|
+
const descriptorSnapshot = safeGetOwnPropertyDescriptorForSourceSnapshot(value, key, `${options.path}.${key}`, options.accessorCode, options.accessorMessage);
|
|
17526
|
+
violations.push(...descriptorSnapshot.violations);
|
|
17527
|
+
const descriptor = descriptorSnapshot.descriptor;
|
|
17528
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
17529
|
+
violations.push(violation(options.accessorCode, `${options.path}.${key}`, options.accessorMessage));
|
|
17530
|
+
continue;
|
|
17531
|
+
}
|
|
17532
|
+
const readSnapshot = safeReadOwnDataPropertyForSourceSnapshot(value, key, descriptor, `${options.path}.${key}`, options.accessorCode, options.accessorMessage);
|
|
17533
|
+
violations.push(...readSnapshot.violations);
|
|
17534
|
+
if (readSnapshot.violations.length > 0) continue;
|
|
17535
|
+
record[key] = readSnapshot.value;
|
|
17536
|
+
}
|
|
17537
|
+
return { record, violations };
|
|
17538
|
+
}
|
|
17539
|
+
function isStableContractIdentifier(value) {
|
|
17540
|
+
if (!hasText(value)) return false;
|
|
17541
|
+
if (looksLikeLocalAbsolutePath(value)) return false;
|
|
17542
|
+
return /^[A-Za-z0-9][A-Za-z0-9._:@-]*$/.test(value);
|
|
17543
|
+
}
|
|
17544
|
+
function looksLikeLocalAbsolutePath(value) {
|
|
17545
|
+
const trimmed = value.trim();
|
|
17546
|
+
if (trimmed.length === 0) return false;
|
|
17547
|
+
return sourceValueVariants(trimmed).some((candidate) => {
|
|
17548
|
+
const normalized = candidate.replace(/\\/g, "/");
|
|
17549
|
+
return /^file:\/*/i.test(normalized) || /[A-Za-z]:\//.test(normalized) || normalized.startsWith("/") || /(?:^|[^A-Za-z0-9])file:\/*/i.test(normalized) || /(?:^|[^A-Za-z0-9])\/(?!\/)/.test(normalized) || /(?:^|[^A-Za-z0-9])(?:Users|home|root|tmp|var|data)(?:\/|$)/.test(normalized) || /(?:^|\/(?:\/*))(?:Users|home|root|tmp|var|data)(?:\/|$)/.test(normalized) || /(?:^|[^A-Za-z0-9])\\\\/.test(candidate);
|
|
17550
|
+
});
|
|
17551
|
+
}
|
|
17552
|
+
function looksLikePrivacySensitiveSourceValue(value, fieldName = "") {
|
|
17553
|
+
if (typeof value !== "string") return false;
|
|
17554
|
+
const trimmed = value.trim();
|
|
17555
|
+
if (trimmed.length === 0) return false;
|
|
17556
|
+
return sourceValueVariants(trimmed).some((candidate) => {
|
|
17557
|
+
const normalized = candidate.replace(/\\/g, "/");
|
|
17558
|
+
return looksLikeLocalAbsolutePath(candidate) || /(?:^|[\s:=,;|()[\]{}<>"'`])~(?:\/|$)/.test(normalized) || /(?:^|\/)\.hermes(?:\/|$)/i.test(normalized) || /(?:^|[^A-Za-z0-9])state\.db(?:$|[^A-Za-z0-9])/i.test(normalized) || looksLikeCredentialAssignment(candidate) || looksLikeBareCredentialToken(candidate) || looksLikeCredentialFieldValue(fieldName, candidate) || /\bBearer\s+(?!\[?redacted\]?(?:$|[\s&;,|()[\]{}<>"'`]))[A-Za-z0-9._~+/=-]{3,}/i.test(candidate) || /(?:^|[\s:=,;|()[\]{}<>"'`])[a-z][a-z0-9+.-]*:\/\/[^\s/@:]+:[^\s/@]+@/i.test(candidate) || /(?:^|[\s:=,;|()[\]{}<>"'`])[a-z][a-z0-9+.-]*:\/\/:[^\s/@]+@/i.test(candidate);
|
|
17559
|
+
});
|
|
17560
|
+
}
|
|
17561
|
+
function violation(code, path12, message) {
|
|
17562
|
+
return { code, path: path12, message };
|
|
17563
|
+
}
|
|
17564
|
+
function looksLikeCredentialAssignment(value) {
|
|
17565
|
+
return /(?:^|[^A-Za-z0-9_-])(?:api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|secret|password|passwd|authorization|token)\s*[:=]\s*(?!(?:Bearer\s+)?\[?redacted\]?(?:$|[\s&;,|()[\]{}<>"'`]))(?:Bearer\s+)?[^\s&;,|]{3,}/i.test(value);
|
|
17566
|
+
}
|
|
17567
|
+
function looksLikeBareCredentialToken(value) {
|
|
17568
|
+
if (/^\[?redacted\]?$/i.test(value.trim())) return false;
|
|
17569
|
+
const tokenBoundary = "(?:^|[^A-Za-z0-9_-])";
|
|
17570
|
+
const tokenEnd = "(?:$|[^A-Za-z0-9_-])";
|
|
17571
|
+
return [
|
|
17572
|
+
new RegExp(`${tokenBoundary}sk[-_][A-Za-z0-9][A-Za-z0-9_-]{2,}${tokenEnd}`),
|
|
17573
|
+
new RegExp(`${tokenBoundary}gh[a-z]_[A-Za-z0-9_]{3,}${tokenEnd}`),
|
|
17574
|
+
new RegExp(`${tokenBoundary}github_pat_[A-Za-z0-9_]{3,}${tokenEnd}`),
|
|
17575
|
+
new RegExp(`${tokenBoundary}xox[abprs]-[A-Za-z0-9-]{3,}${tokenEnd}`),
|
|
17576
|
+
new RegExp(`${tokenBoundary}hf_[A-Za-z0-9]{3,}${tokenEnd}`),
|
|
17577
|
+
new RegExp(`${tokenBoundary}glpat-[A-Za-z0-9_-]{3,}${tokenEnd}`),
|
|
17578
|
+
new RegExp(`${tokenBoundary}(?:AKIA|ASIA)[A-Z0-9]{8,}${tokenEnd}`),
|
|
17579
|
+
new RegExp(`${tokenBoundary}AIza[A-Za-z0-9_-]{3,}${tokenEnd}`),
|
|
17580
|
+
new RegExp(`${tokenBoundary}eyJ[A-Za-z0-9_-]*.[A-Za-z0-9_-]{3,}.[A-Za-z0-9_-]{3,}${tokenEnd}`)
|
|
17581
|
+
].some((pattern) => pattern.test(value));
|
|
17582
|
+
}
|
|
17583
|
+
function sourceValueVariants(value) {
|
|
17584
|
+
const variants = /* @__PURE__ */ new Set([value]);
|
|
17585
|
+
let current = value;
|
|
17586
|
+
for (let attempt = 0; attempt < 5 && current.includes("%"); attempt += 1) {
|
|
17587
|
+
const decoded = decodePercentEncodedBestEffort(current);
|
|
17588
|
+
if (decoded === current) break;
|
|
17589
|
+
variants.add(decoded);
|
|
17590
|
+
current = decoded;
|
|
17591
|
+
}
|
|
17592
|
+
return [...variants];
|
|
17593
|
+
}
|
|
17594
|
+
function decodePercentEncodedBestEffort(value) {
|
|
17595
|
+
try {
|
|
17596
|
+
return decodeURIComponent(value);
|
|
17597
|
+
} catch {
|
|
17598
|
+
return value.replace(/%[0-9A-Fa-f]{2}/g, (match) => String.fromCharCode(Number.parseInt(match.slice(1), 16)));
|
|
17599
|
+
}
|
|
17600
|
+
}
|
|
17601
|
+
function looksLikeCredentialFieldValue(fieldName, value) {
|
|
17602
|
+
return /^(?:api[_-]?key|access[_-]?token|auth[_-]?token|client[_-]?secret|secret|password|passwd|authorization|token)$/i.test(fieldName.trim()) && !/^\[?redacted\]?$/i.test(value.trim());
|
|
17603
|
+
}
|
|
17604
|
+
function isOneOf(values, value) {
|
|
17605
|
+
return typeof value === "string" && values.includes(value);
|
|
17606
|
+
}
|
|
17607
|
+
|
|
17608
|
+
// src/core/source/source-ref.ts
|
|
17609
|
+
function createSourceRef(ref) {
|
|
17610
|
+
const prepared = prepareSourceRef(ref);
|
|
17611
|
+
if (prepared.violations.length > 0 || !prepared.record) {
|
|
17612
|
+
throw new SourceContractValidationError(prepared.violations);
|
|
17613
|
+
}
|
|
17614
|
+
const kind = getOwnField(prepared.record, "kind");
|
|
17615
|
+
const stableId = getOwnField(prepared.record, "stableId");
|
|
17616
|
+
const publicHandle = getOwnField(prepared.record, "publicHandle");
|
|
17617
|
+
const evidenceHandle = getOwnField(prepared.record, "evidenceHandle");
|
|
17618
|
+
const privacyClass = getOwnField(prepared.record, "privacyClass");
|
|
17619
|
+
const captureMode = getOwnField(prepared.record, "captureMode");
|
|
17620
|
+
return Object.freeze({
|
|
17621
|
+
kind,
|
|
17622
|
+
stableId,
|
|
17623
|
+
publicHandle,
|
|
17624
|
+
...evidenceHandle !== void 0 ? { evidenceHandle } : {},
|
|
17625
|
+
privacyClass,
|
|
17626
|
+
captureMode,
|
|
17627
|
+
...prepared.metadata !== void 0 ? { metadata: prepared.metadata } : {}
|
|
17628
|
+
});
|
|
17629
|
+
}
|
|
17630
|
+
function validateSourceRef(ref, path12 = "sourceRef") {
|
|
17631
|
+
return prepareSourceRef(ref, path12).violations;
|
|
17632
|
+
}
|
|
17633
|
+
function prepareSourceRef(ref, path12 = "sourceRef") {
|
|
17634
|
+
const snapshot = snapshotAllowedRecordFields(ref, ["kind", "stableId", "publicHandle", "evidenceHandle", "privacyClass", "captureMode", "metadata"], {
|
|
17635
|
+
path: path12,
|
|
17636
|
+
requiredCode: "sourceRef.required",
|
|
17637
|
+
requiredMessage: "Source ref is required.",
|
|
17638
|
+
unknownCode: "sourceRef.unknown_field",
|
|
17639
|
+
unknownMessage: "Source ref contains unsupported fields.",
|
|
17640
|
+
accessorCode: "sourceRef.accessor_field",
|
|
17641
|
+
accessorMessage: "Source ref fields must be data properties."
|
|
17642
|
+
});
|
|
17643
|
+
if (!snapshot.record) {
|
|
17644
|
+
return { violations: snapshot.violations };
|
|
17645
|
+
}
|
|
17646
|
+
const violations = [...snapshot.violations];
|
|
17647
|
+
const kind = getOwnField(snapshot.record, "kind");
|
|
17648
|
+
const stableId = getOwnField(snapshot.record, "stableId");
|
|
17649
|
+
const publicHandle = getOwnField(snapshot.record, "publicHandle");
|
|
17650
|
+
const evidenceHandle = getOwnField(snapshot.record, "evidenceHandle");
|
|
17651
|
+
const privacyClass = getOwnField(snapshot.record, "privacyClass");
|
|
17652
|
+
const captureMode = getOwnField(snapshot.record, "captureMode");
|
|
17653
|
+
const metadata = getOwnField(snapshot.record, "metadata");
|
|
17654
|
+
if (!hasText(kind)) {
|
|
17655
|
+
violations.push(violation("sourceRef.kind.required", `${path12}.kind`, "Source ref kind must be non-empty."));
|
|
17656
|
+
} else if (looksLikePrivacySensitiveSourceValue(kind)) {
|
|
17657
|
+
violations.push(violation("sourceRef.kind.privacy_sensitive", `${path12}.kind`, "Source ref kind must not leak local state handles or credential-shaped values."));
|
|
17658
|
+
}
|
|
17659
|
+
if (!hasText(stableId)) {
|
|
17660
|
+
violations.push(violation("sourceRef.stableId.required", `${path12}.stableId`, "Source ref stableId must be non-empty."));
|
|
17661
|
+
} else {
|
|
17662
|
+
if (looksLikeLocalAbsolutePath(stableId)) {
|
|
17663
|
+
violations.push(violation("sourceRef.stableId.absolute_local_path", `${path12}.stableId`, "Source ref stableId must not be a local absolute path."));
|
|
17664
|
+
}
|
|
17665
|
+
if (looksLikePrivacySensitiveSourceValue(stableId)) {
|
|
17666
|
+
violations.push(violation("sourceRef.stableId.privacy_sensitive", `${path12}.stableId`, "Source ref stableId must not leak local state handles or credential-shaped values."));
|
|
17667
|
+
}
|
|
17668
|
+
}
|
|
17669
|
+
validatePublicHandle(publicHandle, `${path12}.publicHandle`, violations);
|
|
17670
|
+
validateEvidenceHandle(evidenceHandle, `${path12}.evidenceHandle`, violations);
|
|
17671
|
+
if (!isSourcePrivacyClass(privacyClass)) {
|
|
17672
|
+
violations.push(violation("sourceRef.privacyClass.invalid", `${path12}.privacyClass`, "Source ref privacyClass must be one of the bounded source privacy classes."));
|
|
17673
|
+
}
|
|
17674
|
+
if (!isSourceCaptureMode(captureMode)) {
|
|
17675
|
+
violations.push(violation("sourceRef.captureMode.invalid", `${path12}.captureMode`, "Source ref captureMode must be one of the bounded source capture modes."));
|
|
17676
|
+
}
|
|
17677
|
+
const metadataSnapshot = snapshotSourceRefMetadata(metadata, `${path12}.metadata`);
|
|
17678
|
+
violations.push(...metadataSnapshot.violations);
|
|
17679
|
+
return {
|
|
17680
|
+
record: snapshot.record,
|
|
17681
|
+
metadata: metadataSnapshot.record,
|
|
17682
|
+
violations
|
|
17683
|
+
};
|
|
17684
|
+
}
|
|
17685
|
+
function validatePublicHandle(value, path12, violations) {
|
|
17686
|
+
if (!hasText(value)) {
|
|
17687
|
+
violations.push(violation("sourceRef.publicHandle.required", path12, "Source ref publicHandle must be non-empty."));
|
|
17688
|
+
return;
|
|
17689
|
+
}
|
|
17690
|
+
if (looksLikeLocalAbsolutePath(value)) {
|
|
17691
|
+
violations.push(violation("sourceRef.publicHandle.absolute_local_path", path12, "Source ref publicHandle must not leak a local absolute path."));
|
|
17692
|
+
}
|
|
17693
|
+
if (looksLikePrivacySensitiveSourceValue(value)) {
|
|
17694
|
+
violations.push(violation("sourceRef.publicHandle.privacy_sensitive", path12, "Source ref publicHandle must not leak local state handles or credential-shaped values."));
|
|
17695
|
+
}
|
|
17696
|
+
}
|
|
17697
|
+
function validateEvidenceHandle(value, path12, violations) {
|
|
17698
|
+
if (value === void 0) return;
|
|
17699
|
+
if (!hasText(value)) {
|
|
17700
|
+
violations.push(violation("sourceRef.evidenceHandle.invalid", path12, "Source ref evidenceHandle must be a non-empty string when present."));
|
|
17701
|
+
return;
|
|
17702
|
+
}
|
|
17703
|
+
if (looksLikeLocalAbsolutePath(value)) {
|
|
17704
|
+
violations.push(violation("sourceRef.evidenceHandle.absolute_local_path", path12, "Source ref evidenceHandle must not leak a local absolute path."));
|
|
17705
|
+
}
|
|
17706
|
+
if (looksLikePrivacySensitiveSourceValue(value)) {
|
|
17707
|
+
violations.push(violation("sourceRef.evidenceHandle.privacy_sensitive", path12, "Source ref evidenceHandle must not leak local state handles or credential-shaped values."));
|
|
17708
|
+
}
|
|
17709
|
+
}
|
|
17710
|
+
function snapshotSourceRefMetadata(metadata, path12) {
|
|
17711
|
+
if (metadata === void 0) return { violations: [] };
|
|
17712
|
+
if (!isRecord6(metadata)) {
|
|
17713
|
+
return {
|
|
17714
|
+
violations: [violation("sourceRef.metadata.invalid", path12, "Source ref metadata must be an object when present.")]
|
|
17715
|
+
};
|
|
17716
|
+
}
|
|
17717
|
+
const snapshot = /* @__PURE__ */ Object.create(null);
|
|
17718
|
+
const keySnapshot = safeOwnKeysForSourceSnapshot(metadata, path12, "sourceRef.metadata.accessor_field", "Source ref metadata values must be data properties.");
|
|
17719
|
+
const violations = [...keySnapshot.violations];
|
|
17720
|
+
keySnapshot.keys.forEach((key, index) => {
|
|
17721
|
+
if (typeof key !== "string") {
|
|
17722
|
+
const valuePath2 = `${path12}.[symbol-${index}]`;
|
|
17723
|
+
violations.push(violation("sourceRef.metadata.invalid_key", valuePath2, "Source ref metadata keys must be strings."));
|
|
17724
|
+
const descriptorSnapshot2 = safeGetOwnPropertyDescriptorForSourceSnapshot(metadata, key, valuePath2, "sourceRef.metadata.accessor_field", "Source ref metadata values must be data properties.");
|
|
17725
|
+
violations.push(...descriptorSnapshot2.violations);
|
|
17726
|
+
const descriptor2 = descriptorSnapshot2.descriptor;
|
|
17727
|
+
if (!descriptor2 || !("value" in descriptor2)) {
|
|
17728
|
+
violations.push(violation("sourceRef.metadata.accessor_field", valuePath2, "Source ref metadata values must be data properties."));
|
|
17729
|
+
return;
|
|
17730
|
+
}
|
|
17731
|
+
const readSnapshot2 = safeReadOwnDataPropertyForSourceSnapshot(metadata, key, descriptor2, valuePath2, "sourceRef.metadata.accessor_field", "Source ref metadata values must be data properties.");
|
|
17732
|
+
violations.push(...readSnapshot2.violations);
|
|
17733
|
+
if (readSnapshot2.violations.length > 0) return;
|
|
17734
|
+
validateMetadataValue(readSnapshot2.value, valuePath2, propertyKeyDescription(key), violations);
|
|
17735
|
+
return;
|
|
17736
|
+
}
|
|
17737
|
+
const valuePath = metadataEntryPath(path12, key, index);
|
|
17738
|
+
if (looksLikePrivacySensitiveSourceValue(key)) {
|
|
17739
|
+
violations.push(violation("sourceRef.metadata.privacy_sensitive", valuePath, "Source ref metadata keys must not leak local state handles or credential-shaped values."));
|
|
17740
|
+
}
|
|
17741
|
+
const descriptorSnapshot = safeGetOwnPropertyDescriptorForSourceSnapshot(metadata, key, valuePath, "sourceRef.metadata.accessor_field", "Source ref metadata values must be data properties.");
|
|
17742
|
+
violations.push(...descriptorSnapshot.violations);
|
|
17743
|
+
const descriptor = descriptorSnapshot.descriptor;
|
|
17744
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
17745
|
+
violations.push(violation("sourceRef.metadata.accessor_field", valuePath, "Source ref metadata values must be data properties."));
|
|
17746
|
+
return;
|
|
17747
|
+
}
|
|
17748
|
+
const readSnapshot = safeReadOwnDataPropertyForSourceSnapshot(metadata, key, descriptor, valuePath, "sourceRef.metadata.accessor_field", "Source ref metadata values must be data properties.");
|
|
17749
|
+
violations.push(...readSnapshot.violations);
|
|
17750
|
+
if (readSnapshot.violations.length > 0) return;
|
|
17751
|
+
validateMetadataValue(readSnapshot.value, valuePath, key, violations);
|
|
17752
|
+
if (isSourceRefMetadataValue(readSnapshot.value)) {
|
|
17753
|
+
snapshot[key] = readSnapshot.value;
|
|
17754
|
+
}
|
|
17755
|
+
});
|
|
17756
|
+
return { record: Object.freeze(snapshot), violations };
|
|
17757
|
+
}
|
|
17758
|
+
function isSourceRefMetadataValue(value) {
|
|
17759
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
17760
|
+
}
|
|
17761
|
+
function validateMetadataValue(value, valuePath, key, violations) {
|
|
17762
|
+
if (value === null) return;
|
|
17763
|
+
if (typeof value === "number") {
|
|
17764
|
+
if (!Number.isFinite(value)) {
|
|
17765
|
+
violations.push(violation("sourceRef.metadata.invalid_value", valuePath, "Source ref metadata numbers must be finite."));
|
|
17766
|
+
}
|
|
17767
|
+
if (looksLikePrivacySensitiveSourceValue(String(value), key)) {
|
|
17768
|
+
violations.push(violation("sourceRef.metadata.privacy_sensitive", valuePath, "Source ref metadata must not leak local state handles or credential-shaped values."));
|
|
17769
|
+
}
|
|
17770
|
+
return;
|
|
17771
|
+
}
|
|
17772
|
+
if (typeof value === "boolean") {
|
|
17773
|
+
if (looksLikePrivacySensitiveSourceValue(String(value), key)) {
|
|
17774
|
+
violations.push(violation("sourceRef.metadata.privacy_sensitive", valuePath, "Source ref metadata must not leak local state handles or credential-shaped values."));
|
|
17775
|
+
}
|
|
17776
|
+
return;
|
|
17777
|
+
}
|
|
17778
|
+
if (typeof value === "string") {
|
|
17779
|
+
if (looksLikePrivacySensitiveSourceValue(value, key)) {
|
|
17780
|
+
violations.push(violation("sourceRef.metadata.privacy_sensitive", valuePath, "Source ref metadata must not leak local state handles or credential-shaped values."));
|
|
17781
|
+
}
|
|
17782
|
+
return;
|
|
17783
|
+
}
|
|
17784
|
+
violations.push(violation("sourceRef.metadata.invalid_value", valuePath, "Source ref metadata values must be scalar strings, finite numbers, booleans, or null."));
|
|
17785
|
+
}
|
|
17786
|
+
function propertyKeyDescription(key) {
|
|
17787
|
+
return typeof key === "symbol" ? String(key.description ?? "") : "";
|
|
17788
|
+
}
|
|
17789
|
+
function metadataEntryPath(path12, key, index) {
|
|
17790
|
+
if (looksLikePrivacySensitiveSourceValue(key)) {
|
|
17791
|
+
return `${path12}.[redacted-key-${index}]`;
|
|
17792
|
+
}
|
|
17793
|
+
const sanitizedKey = key.replace(/[^A-Za-z0-9._:-]+/g, "_").slice(0, 64);
|
|
17794
|
+
return sanitizedKey ? `${path12}.${sanitizedKey}` : `${path12}.${index}`;
|
|
17795
|
+
}
|
|
17796
|
+
|
|
17797
|
+
// src/core/source/source-transformations.ts
|
|
17798
|
+
var MAX_SOURCE_TRANSFORMATION_DECLARATIONS = 1e3;
|
|
17799
|
+
var SOURCE_TRANSFORMATION_KINDS = Object.freeze(["extract", "normalize", "privacy-filter", "map", "enrich"]);
|
|
17800
|
+
function isSourceTransformationKind(value) {
|
|
17801
|
+
return typeof value === "string" && SOURCE_TRANSFORMATION_KINDS.includes(value);
|
|
17802
|
+
}
|
|
17803
|
+
function defineSourceTransformation(transformation) {
|
|
17804
|
+
const prepared = prepareSourceTransformationDeclaration(transformation);
|
|
17805
|
+
if (prepared.violations.length > 0 || !prepared.record) {
|
|
17806
|
+
throw new SourceContractValidationError(prepared.violations);
|
|
17807
|
+
}
|
|
17808
|
+
return Object.freeze(freezeSourceTransformationDeclaration(prepared.record));
|
|
17809
|
+
}
|
|
17810
|
+
function defineSourceTransformations(transformations) {
|
|
17811
|
+
const prepared = prepareSourceTransformationDeclarations(transformations, "transformations");
|
|
17812
|
+
if (prepared.violations.length > 0) {
|
|
17813
|
+
throw new SourceContractValidationError(prepared.violations);
|
|
17814
|
+
}
|
|
17815
|
+
return Object.freeze(prepared.records.map((transformation) => Object.freeze(freezeSourceTransformationDeclaration(transformation))));
|
|
17816
|
+
}
|
|
17817
|
+
function validateSourceTransformationDeclarations(transformations, path12 = "transformations") {
|
|
17818
|
+
return prepareSourceTransformationDeclarations(transformations, path12).violations;
|
|
17819
|
+
}
|
|
17820
|
+
function prepareSourceTransformationDeclarations(transformations, path12) {
|
|
17821
|
+
const snapshot = snapshotSourceTransformationArray(transformations, path12);
|
|
17822
|
+
const violations = [...snapshot.violations];
|
|
17823
|
+
const records = [];
|
|
17824
|
+
for (let index = 0; index < snapshot.items.length; index += 1) {
|
|
17825
|
+
const prepared = prepareSourceTransformationDeclaration(snapshot.items[index], `${path12}.${index}`);
|
|
17826
|
+
violations.push(...prepared.violations);
|
|
17827
|
+
if (prepared.record) records[index] = prepared.record;
|
|
17828
|
+
}
|
|
17829
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
17830
|
+
for (const transformation of records) {
|
|
17831
|
+
if (!isRecord6(transformation)) continue;
|
|
17832
|
+
const id = getOwnField(transformation, "id");
|
|
17833
|
+
if (!hasText(id)) continue;
|
|
17834
|
+
if (seenIds.has(id)) {
|
|
17835
|
+
violations.push(violation("transformation.id.duplicate", path12, "Duplicate transformation id."));
|
|
17836
|
+
}
|
|
17837
|
+
seenIds.add(id);
|
|
17838
|
+
}
|
|
17839
|
+
return { records, violations };
|
|
17840
|
+
}
|
|
17841
|
+
function snapshotSourceTransformationArray(transformations, path12) {
|
|
17842
|
+
if (!isArrayForSourceSnapshot(transformations)) {
|
|
17843
|
+
return {
|
|
17844
|
+
items: [],
|
|
17845
|
+
violations: [violation("transformations.required", path12, "At least one source transformation declaration is required.")]
|
|
17846
|
+
};
|
|
17847
|
+
}
|
|
17848
|
+
const lengthDescriptorSnapshot = safeGetOwnPropertyDescriptorForSourceSnapshot(
|
|
17849
|
+
transformations,
|
|
17850
|
+
"length",
|
|
17851
|
+
path12,
|
|
17852
|
+
"transformations.length.invalid",
|
|
17853
|
+
"Source transformation declarations must expose a stable bounded array length."
|
|
17854
|
+
);
|
|
17855
|
+
if (lengthDescriptorSnapshot.violations.length > 0) {
|
|
17856
|
+
return { items: [], violations: lengthDescriptorSnapshot.violations };
|
|
17857
|
+
}
|
|
17858
|
+
const lengthDescriptor = lengthDescriptorSnapshot.descriptor;
|
|
17859
|
+
if (!lengthDescriptor || !("value" in lengthDescriptor)) {
|
|
17860
|
+
return {
|
|
17861
|
+
items: [],
|
|
17862
|
+
violations: [violation("transformations.length.invalid", path12, "Source transformation declarations must expose a stable bounded array length.")]
|
|
17863
|
+
};
|
|
17864
|
+
}
|
|
17865
|
+
const lengthReadSnapshot = safeReadOwnDataPropertyForSourceSnapshot(
|
|
17866
|
+
transformations,
|
|
17867
|
+
"length",
|
|
17868
|
+
lengthDescriptor,
|
|
17869
|
+
path12,
|
|
17870
|
+
"transformations.length.invalid",
|
|
17871
|
+
"Source transformation declarations must expose a stable bounded array length."
|
|
17872
|
+
);
|
|
17873
|
+
if (lengthReadSnapshot.violations.length > 0) {
|
|
17874
|
+
return { items: [], violations: lengthReadSnapshot.violations };
|
|
17875
|
+
}
|
|
17876
|
+
const length = lengthReadSnapshot.value;
|
|
17877
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > MAX_SOURCE_TRANSFORMATION_DECLARATIONS) {
|
|
17878
|
+
return {
|
|
17879
|
+
items: [],
|
|
17880
|
+
violations: [violation("transformations.length.invalid", path12, "Source transformation declarations must expose a stable bounded array length.")]
|
|
17881
|
+
};
|
|
17882
|
+
}
|
|
17883
|
+
if (length === 0) {
|
|
17884
|
+
return {
|
|
17885
|
+
items: [],
|
|
17886
|
+
violations: [violation("transformations.required", path12, "At least one source transformation declaration is required.")]
|
|
17887
|
+
};
|
|
17888
|
+
}
|
|
17889
|
+
const items = new Array(length);
|
|
17890
|
+
const violations = [];
|
|
17891
|
+
for (let index = 0; index < length; index += 1) {
|
|
17892
|
+
const itemPath = `${path12}.${index}`;
|
|
17893
|
+
const descriptorSnapshot = safeGetOwnPropertyDescriptorForSourceSnapshot(
|
|
17894
|
+
transformations,
|
|
17895
|
+
String(index),
|
|
17896
|
+
itemPath,
|
|
17897
|
+
"transformations.accessor_field",
|
|
17898
|
+
"Source transformation declaration array entries must be data properties."
|
|
17899
|
+
);
|
|
17900
|
+
violations.push(...descriptorSnapshot.violations);
|
|
17901
|
+
const descriptor = descriptorSnapshot.descriptor;
|
|
17902
|
+
if (!descriptor) {
|
|
17903
|
+
violations.push(violation("transformations.missing_index", itemPath, "Source transformation declarations must be a dense array."));
|
|
17904
|
+
continue;
|
|
17905
|
+
}
|
|
17906
|
+
if (!("value" in descriptor)) {
|
|
17907
|
+
violations.push(violation("transformations.accessor_field", itemPath, "Source transformation declaration array entries must be data properties."));
|
|
17908
|
+
continue;
|
|
17909
|
+
}
|
|
17910
|
+
const readSnapshot = safeReadOwnDataPropertyForSourceSnapshot(
|
|
17911
|
+
transformations,
|
|
17912
|
+
String(index),
|
|
17913
|
+
descriptor,
|
|
17914
|
+
itemPath,
|
|
17915
|
+
"transformations.accessor_field",
|
|
17916
|
+
"Source transformation declaration array entries must be data properties."
|
|
17917
|
+
);
|
|
17918
|
+
violations.push(...readSnapshot.violations);
|
|
17919
|
+
if (readSnapshot.violations.length > 0) continue;
|
|
17920
|
+
items[index] = readSnapshot.value;
|
|
17921
|
+
}
|
|
17922
|
+
return { items, violations };
|
|
17923
|
+
}
|
|
17924
|
+
function validateSourceTransformationDeclaration(transformation, path12 = "transformation") {
|
|
17925
|
+
return prepareSourceTransformationDeclaration(transformation, path12).violations;
|
|
17926
|
+
}
|
|
17927
|
+
function prepareSourceTransformationDeclaration(transformation, path12 = "transformation") {
|
|
17928
|
+
const snapshot = snapshotAllowedRecordFields(transformation, ["id", "version", "kind", "inputSchema", "outputSchema", "deterministic", "description"], {
|
|
17929
|
+
path: path12,
|
|
17930
|
+
requiredCode: "transformation.required",
|
|
17931
|
+
requiredMessage: "Source transformation declaration is required.",
|
|
17932
|
+
unknownCode: "transformation.unknown_field",
|
|
17933
|
+
unknownMessage: "Source transformation declaration contains unsupported fields.",
|
|
17934
|
+
accessorCode: "transformation.accessor_field",
|
|
17935
|
+
accessorMessage: "Source transformation declaration fields must be data properties."
|
|
17936
|
+
});
|
|
17937
|
+
if (!snapshot.record) return { violations: snapshot.violations };
|
|
17938
|
+
const violations = [...snapshot.violations];
|
|
17939
|
+
const id = getOwnField(snapshot.record, "id");
|
|
17940
|
+
const version = getOwnField(snapshot.record, "version");
|
|
17941
|
+
const kind = getOwnField(snapshot.record, "kind");
|
|
17942
|
+
const inputSchema = getOwnField(snapshot.record, "inputSchema");
|
|
17943
|
+
const outputSchema = getOwnField(snapshot.record, "outputSchema");
|
|
17944
|
+
const deterministic = getOwnField(snapshot.record, "deterministic");
|
|
17945
|
+
const description = getOwnField(snapshot.record, "description");
|
|
17946
|
+
if (!hasText(id)) {
|
|
17947
|
+
violations.push(violation("transformation.id.required", `${path12}.id`, "Source transformation id must be non-empty."));
|
|
17948
|
+
} else {
|
|
17949
|
+
if (!isStableContractIdentifier(id)) {
|
|
17950
|
+
violations.push(violation("transformation.id.unstable", `${path12}.id`, "Source transformation id must be stable and must not include a local absolute path."));
|
|
17951
|
+
}
|
|
17952
|
+
if (looksLikePrivacySensitiveSourceValue(id)) {
|
|
17953
|
+
violations.push(violation("transformation.id.privacy_sensitive", `${path12}.id`, "Source transformation id must not leak local state handles or credential-shaped values."));
|
|
17954
|
+
}
|
|
17955
|
+
}
|
|
17956
|
+
if (!hasText(version)) {
|
|
17957
|
+
violations.push(violation("transformation.version.required", `${path12}.version`, "Source transformation version must be non-empty."));
|
|
17958
|
+
} else if (looksLikePrivacySensitiveSourceValue(version)) {
|
|
17959
|
+
violations.push(violation("transformation.version.privacy_sensitive", `${path12}.version`, "Source transformation version must not leak local state handles or credential-shaped values."));
|
|
17960
|
+
}
|
|
17961
|
+
if (!isSourceTransformationKind(kind)) {
|
|
17962
|
+
violations.push(violation("transformation.kind.invalid", `${path12}.kind`, "Source transformation kind must be one of the bounded source transformation kinds."));
|
|
17963
|
+
}
|
|
17964
|
+
if (!hasText(inputSchema)) {
|
|
17965
|
+
violations.push(violation("transformation.inputSchema.required", `${path12}.inputSchema`, "Source transformation inputSchema must be non-empty."));
|
|
17966
|
+
} else if (looksLikePrivacySensitiveSourceValue(inputSchema)) {
|
|
17967
|
+
violations.push(violation("transformation.inputSchema.privacy_sensitive", `${path12}.inputSchema`, "Source transformation inputSchema must not leak local state handles or credential-shaped values."));
|
|
17968
|
+
}
|
|
17969
|
+
if (!hasText(outputSchema)) {
|
|
17970
|
+
violations.push(violation("transformation.outputSchema.required", `${path12}.outputSchema`, "Source transformation outputSchema must be non-empty."));
|
|
17971
|
+
} else if (looksLikePrivacySensitiveSourceValue(outputSchema)) {
|
|
17972
|
+
violations.push(violation("transformation.outputSchema.privacy_sensitive", `${path12}.outputSchema`, "Source transformation outputSchema must not leak local state handles or credential-shaped values."));
|
|
17973
|
+
}
|
|
17974
|
+
if (deterministic !== void 0 && typeof deterministic !== "boolean") {
|
|
17975
|
+
violations.push(violation("transformation.deterministic.invalid", `${path12}.deterministic`, "Source transformation deterministic must be a boolean when present."));
|
|
17976
|
+
}
|
|
17977
|
+
if (description !== void 0 && typeof description !== "string") {
|
|
17978
|
+
violations.push(violation("transformation.description.invalid", `${path12}.description`, "Source transformation description must be a string when present."));
|
|
17979
|
+
} else if (hasText(description) && looksLikePrivacySensitiveSourceValue(description)) {
|
|
17980
|
+
violations.push(violation("transformation.description.privacy_sensitive", `${path12}.description`, "Source transformation description must not leak local state handles or credential-shaped values."));
|
|
17981
|
+
}
|
|
17982
|
+
return { record: snapshot.record, violations };
|
|
17983
|
+
}
|
|
17984
|
+
function freezeSourceTransformationDeclaration(transformation) {
|
|
17985
|
+
const id = getOwnField(transformation, "id");
|
|
17986
|
+
const version = getOwnField(transformation, "version");
|
|
17987
|
+
const kind = getOwnField(transformation, "kind");
|
|
17988
|
+
const inputSchema = getOwnField(transformation, "inputSchema");
|
|
17989
|
+
const outputSchema = getOwnField(transformation, "outputSchema");
|
|
17990
|
+
const deterministic = getOwnField(transformation, "deterministic");
|
|
17991
|
+
const description = getOwnField(transformation, "description");
|
|
17992
|
+
const defined = {
|
|
17993
|
+
id,
|
|
17994
|
+
version,
|
|
17995
|
+
kind,
|
|
17996
|
+
inputSchema,
|
|
17997
|
+
outputSchema
|
|
17998
|
+
};
|
|
17999
|
+
if (deterministic !== void 0) defined.deterministic = deterministic;
|
|
18000
|
+
if (description !== void 0) defined.description = description;
|
|
18001
|
+
return defined;
|
|
18002
|
+
}
|
|
18003
|
+
|
|
18004
|
+
// src/core/source/source-adapter.ts
|
|
18005
|
+
function validateSourceAdapterIdentity(identity, path12 = "identity") {
|
|
18006
|
+
return prepareSourceAdapterIdentity(identity, path12).violations;
|
|
18007
|
+
}
|
|
18008
|
+
function prepareSourceAdapterIdentity(identity, path12 = "identity") {
|
|
18009
|
+
const snapshot = snapshotAllowedRecordFields(identity, ["id", "version", "displayName"], {
|
|
18010
|
+
path: path12,
|
|
18011
|
+
requiredCode: "identity.required",
|
|
18012
|
+
requiredMessage: "Source adapter identity is required.",
|
|
18013
|
+
unknownCode: "identity.unknown_field",
|
|
18014
|
+
unknownMessage: "Source adapter contract object contains unsupported fields.",
|
|
18015
|
+
accessorCode: "identity.accessor_field",
|
|
18016
|
+
accessorMessage: "Source adapter identity fields must be data properties."
|
|
18017
|
+
});
|
|
18018
|
+
if (!snapshot.record) return { violations: snapshot.violations };
|
|
18019
|
+
const violations = [...snapshot.violations];
|
|
18020
|
+
const id = getOwnField(snapshot.record, "id");
|
|
18021
|
+
const version = getOwnField(snapshot.record, "version");
|
|
18022
|
+
const displayName = getOwnField(snapshot.record, "displayName");
|
|
18023
|
+
if (!hasText(id)) {
|
|
18024
|
+
violations.push(violation("identity.id.required", `${path12}.id`, "Source adapter identity id must be non-empty."));
|
|
18025
|
+
} else {
|
|
18026
|
+
if (!isStableContractIdentifier(id)) {
|
|
18027
|
+
violations.push(violation("identity.id.unstable", `${path12}.id`, "Source adapter identity id must be stable and must not include a local absolute path."));
|
|
18028
|
+
}
|
|
18029
|
+
if (looksLikePrivacySensitiveSourceValue(id)) {
|
|
18030
|
+
violations.push(violation("identity.id.privacy_sensitive", `${path12}.id`, "Source adapter identity id must not leak local state handles or credential-shaped values."));
|
|
18031
|
+
}
|
|
18032
|
+
}
|
|
18033
|
+
if (!hasText(version)) {
|
|
18034
|
+
violations.push(violation("identity.version.required", `${path12}.version`, "Source adapter identity version must be non-empty."));
|
|
18035
|
+
} else if (looksLikePrivacySensitiveSourceValue(version)) {
|
|
18036
|
+
violations.push(violation("identity.version.privacy_sensitive", `${path12}.version`, "Source adapter identity version must not leak local state handles or credential-shaped values."));
|
|
18037
|
+
}
|
|
18038
|
+
if (displayName !== void 0 && typeof displayName !== "string") {
|
|
18039
|
+
violations.push(violation("identity.displayName.invalid", `${path12}.displayName`, "Source adapter displayName must be a string when present."));
|
|
18040
|
+
} else if (hasText(displayName) && looksLikePrivacySensitiveSourceValue(displayName)) {
|
|
18041
|
+
violations.push(violation("identity.displayName.privacy_sensitive", `${path12}.displayName`, "Source adapter displayName must not leak local state handles or credential-shaped values."));
|
|
18042
|
+
}
|
|
18043
|
+
return { record: snapshot.record, violations };
|
|
18044
|
+
}
|
|
18045
|
+
function freezeSourceAdapterIdentity(identity) {
|
|
18046
|
+
const id = getOwnField(identity, "id");
|
|
18047
|
+
const version = getOwnField(identity, "version");
|
|
18048
|
+
const displayName = getOwnField(identity, "displayName");
|
|
18049
|
+
const defined = {
|
|
18050
|
+
id,
|
|
18051
|
+
version
|
|
18052
|
+
};
|
|
18053
|
+
if (displayName !== void 0) defined.displayName = displayName;
|
|
18054
|
+
return Object.freeze(defined);
|
|
18055
|
+
}
|
|
18056
|
+
function validateSourceAdapterCapabilities(capabilities, path12 = "capabilities") {
|
|
18057
|
+
return prepareSourceAdapterCapabilities(capabilities, path12).violations;
|
|
18058
|
+
}
|
|
18059
|
+
function prepareSourceAdapterCapabilities(capabilities, path12 = "capabilities") {
|
|
18060
|
+
if (!isRecord6(capabilities)) {
|
|
18061
|
+
return {
|
|
18062
|
+
violations: [violation("capabilities.required", path12, "Source adapter capabilities with currentnessStrategy are required.")]
|
|
18063
|
+
};
|
|
18064
|
+
}
|
|
18065
|
+
const record = /* @__PURE__ */ Object.create(null);
|
|
18066
|
+
const keySnapshot = safeOwnKeysForSourceSnapshot(capabilities, path12, "capabilities.accessor_field", "Source adapter capability fields must be data properties.");
|
|
18067
|
+
const violations = [...keySnapshot.violations];
|
|
18068
|
+
keySnapshot.keys.forEach((key, index) => {
|
|
18069
|
+
if (typeof key !== "string") {
|
|
18070
|
+
const valuePath2 = `${path12}.[symbol-${index}]`;
|
|
18071
|
+
violations.push(violation("capabilities.invalid_key", valuePath2, "Source adapter capability keys must be strings."));
|
|
18072
|
+
const descriptorSnapshot2 = safeGetOwnPropertyDescriptorForSourceSnapshot(capabilities, key, valuePath2, "capabilities.accessor_field", "Source adapter capability fields must be data properties.");
|
|
18073
|
+
violations.push(...descriptorSnapshot2.violations);
|
|
18074
|
+
const descriptor2 = descriptorSnapshot2.descriptor;
|
|
18075
|
+
if (!descriptor2 || !("value" in descriptor2)) {
|
|
18076
|
+
violations.push(violation("capabilities.accessor_field", valuePath2, "Source adapter capability fields must be data properties."));
|
|
18077
|
+
return;
|
|
18078
|
+
}
|
|
18079
|
+
const readSnapshot2 = safeReadOwnDataPropertyForSourceSnapshot(capabilities, key, descriptor2, valuePath2, "capabilities.accessor_field", "Source adapter capability fields must be data properties.");
|
|
18080
|
+
violations.push(...readSnapshot2.violations);
|
|
18081
|
+
if (readSnapshot2.violations.length > 0) return;
|
|
18082
|
+
validateCapabilityValue(propertyKeyDescription2(key), readSnapshot2.value, valuePath2, violations);
|
|
18083
|
+
return;
|
|
18084
|
+
}
|
|
18085
|
+
const valuePath = capabilityEntryPath(path12, key, index);
|
|
18086
|
+
const descriptorSnapshot = safeGetOwnPropertyDescriptorForSourceSnapshot(capabilities, key, valuePath, "capabilities.accessor_field", "Source adapter capability fields must be data properties.");
|
|
18087
|
+
violations.push(...descriptorSnapshot.violations);
|
|
18088
|
+
const descriptor = descriptorSnapshot.descriptor;
|
|
18089
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
18090
|
+
violations.push(violation("capabilities.accessor_field", valuePath, "Source adapter capability fields must be data properties."));
|
|
18091
|
+
return;
|
|
18092
|
+
}
|
|
18093
|
+
const readSnapshot = safeReadOwnDataPropertyForSourceSnapshot(capabilities, key, descriptor, valuePath, "capabilities.accessor_field", "Source adapter capability fields must be data properties.");
|
|
18094
|
+
violations.push(...readSnapshot.violations);
|
|
18095
|
+
if (readSnapshot.violations.length > 0) return;
|
|
18096
|
+
record[key] = readSnapshot.value;
|
|
18097
|
+
validateCapabilityValue(key, readSnapshot.value, valuePath, violations);
|
|
18098
|
+
});
|
|
18099
|
+
const currentnessStrategy = getOwnField(record, "currentnessStrategy");
|
|
18100
|
+
if (!hasOwnField(record, "currentnessStrategy") || !hasText(currentnessStrategy)) {
|
|
18101
|
+
violations.push(violation("capabilities.currentnessStrategy.required", `${path12}.currentnessStrategy`, "Source adapter capabilities must declare a deterministic currentnessStrategy."));
|
|
18102
|
+
} else if (!isStableContractIdentifier(currentnessStrategy)) {
|
|
18103
|
+
violations.push(violation("capabilities.currentnessStrategy.unstable", `${path12}.currentnessStrategy`, "Source adapter currentnessStrategy must be stable and must not include a local absolute path."));
|
|
18104
|
+
}
|
|
18105
|
+
return { record, violations };
|
|
18106
|
+
}
|
|
18107
|
+
function freezeSourceAdapterCapabilities(capabilities) {
|
|
18108
|
+
const keySnapshot = safeOwnKeysForSourceSnapshot(capabilities, "capabilities", "capabilities.accessor_field", "Source adapter capability fields must be data properties.");
|
|
18109
|
+
const safeEntries = [];
|
|
18110
|
+
keySnapshot.keys.forEach((key) => {
|
|
18111
|
+
if (typeof key !== "string") return;
|
|
18112
|
+
const descriptorSnapshot = safeGetOwnPropertyDescriptorForSourceSnapshot(capabilities, key, `capabilities.${key}`, "capabilities.accessor_field", "Source adapter capability fields must be data properties.");
|
|
18113
|
+
const descriptor = descriptorSnapshot.descriptor;
|
|
18114
|
+
if (descriptor && "value" in descriptor) safeEntries.push([key, descriptor.value]);
|
|
18115
|
+
});
|
|
18116
|
+
return Object.freeze(Object.fromEntries(safeEntries));
|
|
18117
|
+
}
|
|
18118
|
+
function propertyKeyDescription2(key) {
|
|
18119
|
+
return typeof key === "symbol" ? String(key.description ?? "") : "";
|
|
18120
|
+
}
|
|
18121
|
+
function capabilityEntryPath(path12, key, index) {
|
|
18122
|
+
if (looksLikePrivacySensitiveSourceValue(key)) {
|
|
18123
|
+
return `${path12}.[redacted-key-${index}]`;
|
|
18124
|
+
}
|
|
18125
|
+
const sanitizedKey = key.replace(/[^A-Za-z0-9._:-]+/g, "_").slice(0, 64);
|
|
18126
|
+
return sanitizedKey ? `${path12}.${sanitizedKey}` : `${path12}.${index}`;
|
|
18127
|
+
}
|
|
18128
|
+
function validateCapabilityValue(key, value, path12, violations) {
|
|
18129
|
+
if (looksLikePrivacySensitiveSourceValue(key)) {
|
|
18130
|
+
violations.push(violation("capabilities.privacy_sensitive", path12, "Source adapter capability keys must not leak local state handles or credential-shaped values."));
|
|
18131
|
+
}
|
|
18132
|
+
if (typeof value === "number") {
|
|
18133
|
+
if (!Number.isFinite(value)) {
|
|
18134
|
+
violations.push(violation("capabilities.invalid_value", path12, "Source adapter capability numbers must be finite."));
|
|
18135
|
+
}
|
|
18136
|
+
if (looksLikePrivacySensitiveSourceValue(String(value), key)) {
|
|
18137
|
+
violations.push(violation("capabilities.privacy_sensitive", path12, "Source adapter capability values must not leak local state handles or credential-shaped values."));
|
|
18138
|
+
}
|
|
18139
|
+
return;
|
|
18140
|
+
}
|
|
18141
|
+
if (typeof value === "boolean") return;
|
|
18142
|
+
if (typeof value === "string") {
|
|
18143
|
+
if (looksLikePrivacySensitiveSourceValue(value, key)) {
|
|
18144
|
+
violations.push(violation("capabilities.privacy_sensitive", path12, "Source adapter capability values must not leak local state handles or credential-shaped values."));
|
|
18145
|
+
}
|
|
18146
|
+
return;
|
|
18147
|
+
}
|
|
18148
|
+
violations.push(violation("capabilities.invalid_value", path12, "Source adapter capability values must be scalar strings, finite numbers, or booleans."));
|
|
18149
|
+
}
|
|
18150
|
+
|
|
18151
|
+
// src/core/source/source-adapter-contract-suite.ts
|
|
18152
|
+
var MAX_SAMPLE_SOURCE_REFS = 1e3;
|
|
18153
|
+
function validateSourceAdapterContract(adapter, path12 = "adapter") {
|
|
18154
|
+
return prepareSourceAdapterContract(adapter, path12).violations;
|
|
18155
|
+
}
|
|
18156
|
+
function assertSourceAdapterContract(adapter) {
|
|
18157
|
+
return defineSourceAdapter(adapter);
|
|
18158
|
+
}
|
|
18159
|
+
function defineSourceAdapter(adapter) {
|
|
18160
|
+
const prepared = prepareSourceAdapterContract(adapter);
|
|
18161
|
+
if (prepared.violations.length > 0 || !prepared.contract) {
|
|
18162
|
+
throw new SourceContractValidationError(prepared.violations);
|
|
18163
|
+
}
|
|
18164
|
+
return Object.freeze(prepared.contract);
|
|
18165
|
+
}
|
|
18166
|
+
function prepareSourceAdapterContract(adapter, path12 = "adapter") {
|
|
18167
|
+
const snapshot = snapshotAllowedRecordFields(adapter, ["identity", "source", "transformations", "sampleSourceRefs", "capabilities"], {
|
|
18168
|
+
path: path12,
|
|
18169
|
+
requiredCode: "adapter.required",
|
|
18170
|
+
requiredMessage: "Source adapter contract is required.",
|
|
18171
|
+
unknownCode: "adapter.unknown_field",
|
|
18172
|
+
unknownMessage: "Source adapter contract contains unsupported fields.",
|
|
18173
|
+
accessorCode: "adapter.accessor_field",
|
|
18174
|
+
accessorMessage: "Source adapter contract fields must be data properties."
|
|
18175
|
+
});
|
|
18176
|
+
if (!snapshot.record) return { violations: snapshot.violations };
|
|
18177
|
+
const violations = [...snapshot.violations];
|
|
18178
|
+
const identityInput = getOwnField(snapshot.record, "identity");
|
|
18179
|
+
const sourceInput = getOwnField(snapshot.record, "source");
|
|
18180
|
+
const transformationsInput = getOwnField(snapshot.record, "transformations");
|
|
18181
|
+
const capabilitiesInput = getOwnField(snapshot.record, "capabilities");
|
|
18182
|
+
const sampleSourceRefsInput = getOwnField(snapshot.record, "sampleSourceRefs");
|
|
18183
|
+
const identityPrepared = prepareSourceAdapterIdentity(identityInput, `${path12}.identity`);
|
|
18184
|
+
violations.push(...identityPrepared.violations);
|
|
18185
|
+
const sourcePrepared = defineSafeResult(
|
|
18186
|
+
() => defineSourceSchema(sourceInput),
|
|
18187
|
+
"source",
|
|
18188
|
+
`${path12}.source`,
|
|
18189
|
+
"source.validation_error",
|
|
18190
|
+
"Source schema validation failed unexpectedly."
|
|
18191
|
+
);
|
|
18192
|
+
violations.push(...sourcePrepared.violations);
|
|
18193
|
+
const transformationsPrepared = defineSafeResult(
|
|
18194
|
+
() => defineSourceTransformations(transformationsInput),
|
|
18195
|
+
"transformations",
|
|
18196
|
+
`${path12}.transformations`,
|
|
18197
|
+
"transformations.validation_error",
|
|
18198
|
+
"Source transformation validation failed unexpectedly."
|
|
18199
|
+
);
|
|
18200
|
+
violations.push(...transformationsPrepared.violations);
|
|
18201
|
+
const capabilitiesPrepared = prepareSourceAdapterCapabilities(capabilitiesInput, `${path12}.capabilities`);
|
|
18202
|
+
violations.push(...capabilitiesPrepared.violations);
|
|
18203
|
+
const sampleSourceRefsPrepared = defineSampleSourceRefs(sampleSourceRefsInput, `${path12}.sampleSourceRefs`);
|
|
18204
|
+
violations.push(...sampleSourceRefsPrepared.violations);
|
|
18205
|
+
if (violations.length > 0 || !identityPrepared.record || !sourcePrepared.value || !transformationsPrepared.value || !capabilitiesPrepared.record) {
|
|
18206
|
+
return { violations };
|
|
18207
|
+
}
|
|
18208
|
+
const contract = {
|
|
18209
|
+
identity: freezeSourceAdapterIdentity(identityPrepared.record),
|
|
18210
|
+
source: sourcePrepared.value,
|
|
18211
|
+
transformations: transformationsPrepared.value,
|
|
18212
|
+
sampleSourceRefs: sampleSourceRefsPrepared.value,
|
|
18213
|
+
capabilities: freezeSourceAdapterCapabilities(capabilitiesPrepared.record)
|
|
18214
|
+
};
|
|
18215
|
+
return { contract, violations };
|
|
18216
|
+
}
|
|
18217
|
+
function defineSampleSourceRefs(sampleSourceRefs, path12) {
|
|
18218
|
+
if (sampleSourceRefs === void 0) return { violations: [] };
|
|
18219
|
+
if (!isArrayForSourceSnapshot(sampleSourceRefs)) {
|
|
18220
|
+
return {
|
|
18221
|
+
violations: [violation("sampleSourceRefs.invalid", path12, "sampleSourceRefs must be an array when present.")]
|
|
18222
|
+
};
|
|
18223
|
+
}
|
|
18224
|
+
const snapshot = snapshotSampleSourceRefArray(sampleSourceRefs, path12);
|
|
18225
|
+
const violations = [...snapshot.violations];
|
|
18226
|
+
const refs = [];
|
|
18227
|
+
for (let index = 0; index < snapshot.items.length; index += 1) {
|
|
18228
|
+
const defined = defineSafeResult(
|
|
18229
|
+
() => createSourceRef(snapshot.items[index]),
|
|
18230
|
+
"sourceRef",
|
|
18231
|
+
`${path12}.${index}`,
|
|
18232
|
+
"sampleSourceRefs.validation_error",
|
|
18233
|
+
"Sample source ref validation failed unexpectedly."
|
|
18234
|
+
);
|
|
18235
|
+
violations.push(...defined.violations);
|
|
18236
|
+
if (defined.value) refs[index] = defined.value;
|
|
18237
|
+
}
|
|
18238
|
+
return {
|
|
18239
|
+
value: violations.length === 0 ? Object.freeze(refs) : void 0,
|
|
18240
|
+
violations
|
|
18241
|
+
};
|
|
18242
|
+
}
|
|
18243
|
+
function snapshotSampleSourceRefArray(sampleSourceRefs, path12) {
|
|
18244
|
+
const lengthDescriptorSnapshot = safeGetOwnPropertyDescriptorForSourceSnapshot(
|
|
18245
|
+
sampleSourceRefs,
|
|
18246
|
+
"length",
|
|
18247
|
+
path12,
|
|
18248
|
+
"sampleSourceRefs.length.invalid",
|
|
18249
|
+
"sampleSourceRefs must expose a stable bounded array length."
|
|
18250
|
+
);
|
|
18251
|
+
if (lengthDescriptorSnapshot.violations.length > 0) {
|
|
18252
|
+
return { items: [], violations: lengthDescriptorSnapshot.violations };
|
|
18253
|
+
}
|
|
18254
|
+
const lengthDescriptor = lengthDescriptorSnapshot.descriptor;
|
|
18255
|
+
if (!lengthDescriptor || !("value" in lengthDescriptor)) {
|
|
18256
|
+
return {
|
|
18257
|
+
items: [],
|
|
18258
|
+
violations: [violation("sampleSourceRefs.length.invalid", path12, "sampleSourceRefs must expose a stable bounded array length.")]
|
|
18259
|
+
};
|
|
18260
|
+
}
|
|
18261
|
+
const lengthReadSnapshot = safeReadOwnDataPropertyForSourceSnapshot(
|
|
18262
|
+
sampleSourceRefs,
|
|
18263
|
+
"length",
|
|
18264
|
+
lengthDescriptor,
|
|
18265
|
+
path12,
|
|
18266
|
+
"sampleSourceRefs.length.invalid",
|
|
18267
|
+
"sampleSourceRefs must expose a stable bounded array length."
|
|
18268
|
+
);
|
|
18269
|
+
if (lengthReadSnapshot.violations.length > 0) {
|
|
18270
|
+
return { items: [], violations: lengthReadSnapshot.violations };
|
|
18271
|
+
}
|
|
18272
|
+
const length = lengthReadSnapshot.value;
|
|
18273
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > MAX_SAMPLE_SOURCE_REFS) {
|
|
18274
|
+
return {
|
|
18275
|
+
items: [],
|
|
18276
|
+
violations: [violation("sampleSourceRefs.length.invalid", path12, "sampleSourceRefs must expose a stable bounded array length.")]
|
|
18277
|
+
};
|
|
18278
|
+
}
|
|
18279
|
+
const items = new Array(length);
|
|
18280
|
+
const violations = [];
|
|
18281
|
+
for (let index = 0; index < length; index += 1) {
|
|
18282
|
+
const itemPath = `${path12}.${index}`;
|
|
18283
|
+
const descriptorSnapshot = safeGetOwnPropertyDescriptorForSourceSnapshot(
|
|
18284
|
+
sampleSourceRefs,
|
|
18285
|
+
String(index),
|
|
18286
|
+
itemPath,
|
|
18287
|
+
"sampleSourceRefs.accessor_field",
|
|
18288
|
+
"sampleSourceRefs array entries must be data properties."
|
|
18289
|
+
);
|
|
18290
|
+
violations.push(...descriptorSnapshot.violations);
|
|
18291
|
+
const descriptor = descriptorSnapshot.descriptor;
|
|
18292
|
+
if (!descriptor) {
|
|
18293
|
+
violations.push(violation("sampleSourceRefs.missing_index", itemPath, "sampleSourceRefs must be a dense array."));
|
|
18294
|
+
continue;
|
|
18295
|
+
}
|
|
18296
|
+
if (!("value" in descriptor)) {
|
|
18297
|
+
violations.push(violation("sampleSourceRefs.accessor_field", itemPath, "sampleSourceRefs array entries must be data properties."));
|
|
18298
|
+
continue;
|
|
18299
|
+
}
|
|
18300
|
+
const readSnapshot = safeReadOwnDataPropertyForSourceSnapshot(
|
|
18301
|
+
sampleSourceRefs,
|
|
18302
|
+
String(index),
|
|
18303
|
+
descriptor,
|
|
18304
|
+
itemPath,
|
|
18305
|
+
"sampleSourceRefs.accessor_field",
|
|
18306
|
+
"sampleSourceRefs array entries must be data properties."
|
|
18307
|
+
);
|
|
18308
|
+
violations.push(...readSnapshot.violations);
|
|
18309
|
+
if (readSnapshot.violations.length > 0) continue;
|
|
18310
|
+
items[index] = readSnapshot.value;
|
|
18311
|
+
}
|
|
18312
|
+
return { items, violations };
|
|
18313
|
+
}
|
|
18314
|
+
function defineSafeResult(define, sourcePathPrefix, targetPathPrefix, fallbackCode, fallbackMessage) {
|
|
18315
|
+
try {
|
|
18316
|
+
return { value: define(), violations: [] };
|
|
18317
|
+
} catch (error) {
|
|
18318
|
+
if (error instanceof SourceContractValidationError) {
|
|
18319
|
+
return {
|
|
18320
|
+
violations: error.violations.map((violationItem) => ({
|
|
18321
|
+
...violationItem,
|
|
18322
|
+
path: remapPathPrefix(violationItem.path, sourcePathPrefix, targetPathPrefix)
|
|
18323
|
+
}))
|
|
18324
|
+
};
|
|
18325
|
+
}
|
|
18326
|
+
return { violations: [violation(fallbackCode, targetPathPrefix, fallbackMessage)] };
|
|
18327
|
+
}
|
|
18328
|
+
}
|
|
18329
|
+
function remapPathPrefix(path12, sourcePathPrefix, targetPathPrefix) {
|
|
18330
|
+
if (path12 === sourcePathPrefix) return targetPathPrefix;
|
|
18331
|
+
if (path12.startsWith(`${sourcePathPrefix}.`)) return `${targetPathPrefix}${path12.slice(sourcePathPrefix.length)}`;
|
|
18332
|
+
return path12;
|
|
18333
|
+
}
|
|
18334
|
+
|
|
17241
18335
|
// src/core/evidence-aligner.ts
|
|
17242
18336
|
import { createHash as createHash8 } from "crypto";
|
|
17243
18337
|
var DEFAULT_OPTIONS2 = {
|
|
@@ -17696,10 +18790,11 @@ var ContextCompressor = class {
|
|
|
17696
18790
|
compress(content, options) {
|
|
17697
18791
|
const source = safeLabel(options.source) || "unknown";
|
|
17698
18792
|
const mode = options.mode;
|
|
18793
|
+
const sourceRef = safeSourceRef(options.sourceRef ?? sourceRefFromMetadata(options.metadata ?? {}));
|
|
17699
18794
|
const contentType = detectContextContentType(content, options.metadata ?? {});
|
|
17700
18795
|
const originalLines = nonEmptyLines(content).length;
|
|
17701
18796
|
if (mode === "off") {
|
|
17702
|
-
return result(content, {
|
|
18797
|
+
return withSourceRefHint(result(content, {
|
|
17703
18798
|
mode,
|
|
17704
18799
|
source,
|
|
17705
18800
|
contentType,
|
|
@@ -17708,21 +18803,21 @@ var ContextCompressor = class {
|
|
|
17708
18803
|
originalLines,
|
|
17709
18804
|
omittedLines: 0,
|
|
17710
18805
|
signalCount: 0
|
|
17711
|
-
});
|
|
18806
|
+
}), sourceRef);
|
|
17712
18807
|
}
|
|
17713
18808
|
if (contentType === "log") {
|
|
17714
|
-
return this.compressLog(content, mode, source, contentType);
|
|
18809
|
+
return withSourceRefHint(this.compressLog(content, mode, source, contentType), sourceRef);
|
|
17715
18810
|
}
|
|
17716
18811
|
if (contentType === "markdown") {
|
|
17717
|
-
return this.compressMarkdown(content, mode, source, contentType);
|
|
18812
|
+
return withSourceRefHint(this.compressMarkdown(content, mode, source, contentType), sourceRef);
|
|
17718
18813
|
}
|
|
17719
18814
|
if (contentType === "diff") {
|
|
17720
|
-
return this.compressDiff(content, mode, source, contentType);
|
|
18815
|
+
return withSourceRefHint(this.compressDiff(content, mode, source, contentType), sourceRef);
|
|
17721
18816
|
}
|
|
17722
18817
|
if (contentType === "code") {
|
|
17723
|
-
return this.compressCode(content, mode, source, contentType);
|
|
18818
|
+
return withSourceRefHint(this.compressCode(content, mode, source, contentType), sourceRef);
|
|
17724
18819
|
}
|
|
17725
|
-
return this.compressPlain(content, mode, source, contentType);
|
|
18820
|
+
return withSourceRefHint(this.compressPlain(content, mode, source, contentType), sourceRef);
|
|
17726
18821
|
}
|
|
17727
18822
|
compressLog(content, mode, source, contentType) {
|
|
17728
18823
|
const lines = nonEmptyLines(content);
|
|
@@ -17860,11 +18955,15 @@ function summarizeCompressionTelemetry(metadata) {
|
|
|
17860
18955
|
const totalOriginalChars = metadata.reduce((sum, item) => sum + item.originalChars, 0);
|
|
17861
18956
|
const totalCompressedChars = metadata.reduce((sum, item) => sum + item.compressedChars, 0);
|
|
17862
18957
|
const totalSavedChars = metadata.reduce((sum, item) => sum + item.savedChars, 0);
|
|
18958
|
+
const totalOmittedLines = metadata.reduce((sum, item) => sum + item.omittedLines, 0);
|
|
18959
|
+
const sourceRefsPreserved = metadata.filter((item) => item.sourceRefPreserved).length;
|
|
17863
18960
|
return {
|
|
17864
18961
|
totalItems: metadata.length,
|
|
17865
18962
|
totalOriginalChars,
|
|
17866
18963
|
totalCompressedChars,
|
|
17867
18964
|
totalSavedChars,
|
|
18965
|
+
totalOmittedLines,
|
|
18966
|
+
sourceRefsPreserved,
|
|
17868
18967
|
bySource: summarizeBy(metadata, "source"),
|
|
17869
18968
|
byStrategy: summarizeBy(metadata, "strategy")
|
|
17870
18969
|
};
|
|
@@ -17878,12 +18977,16 @@ function summarizeBy(metadata, key) {
|
|
|
17878
18977
|
items: 0,
|
|
17879
18978
|
originalChars: 0,
|
|
17880
18979
|
compressedChars: 0,
|
|
17881
|
-
savedChars: 0
|
|
18980
|
+
savedChars: 0,
|
|
18981
|
+
omittedLines: 0,
|
|
18982
|
+
sourceRefsPreserved: 0
|
|
17882
18983
|
};
|
|
17883
18984
|
existing.items += 1;
|
|
17884
18985
|
existing.originalChars += item.originalChars;
|
|
17885
18986
|
existing.compressedChars += item.compressedChars;
|
|
17886
18987
|
existing.savedChars += item.savedChars;
|
|
18988
|
+
existing.omittedLines += item.omittedLines;
|
|
18989
|
+
if (item.sourceRefPreserved) existing.sourceRefsPreserved += 1;
|
|
17887
18990
|
groups.set(groupKey, existing);
|
|
17888
18991
|
}
|
|
17889
18992
|
return Array.from(groups.values()).sort((a, b) => String(a[key] ?? "").localeCompare(String(b[key] ?? "")));
|
|
@@ -17898,7 +19001,8 @@ function result(text, base) {
|
|
|
17898
19001
|
...base,
|
|
17899
19002
|
compressedChars,
|
|
17900
19003
|
savedChars,
|
|
17901
|
-
compressionRatio
|
|
19004
|
+
compressionRatio,
|
|
19005
|
+
sourceRefPreserved: false
|
|
17902
19006
|
}
|
|
17903
19007
|
};
|
|
17904
19008
|
}
|
|
@@ -17947,6 +19051,65 @@ function safeLabel(value) {
|
|
|
17947
19051
|
const cleaned = value.replace(/[^A-Za-z0-9_.:-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80);
|
|
17948
19052
|
return cleaned || void 0;
|
|
17949
19053
|
}
|
|
19054
|
+
function sourceRefFromMetadata(metadata) {
|
|
19055
|
+
const explicit = firstString(metadata, ["sourceRef", "source_ref", "sourceReference", "source_reference"]);
|
|
19056
|
+
if (explicit) return explicit;
|
|
19057
|
+
const citation = firstString(metadata, ["citationId", "citation_id", "memoryCitationId", "memory_citation_id"]);
|
|
19058
|
+
if (citation) return citation.startsWith("mem:") ? citation : `mem:${citation}`;
|
|
19059
|
+
const eventId = firstString(metadata, ["eventId", "event_id"]);
|
|
19060
|
+
if (eventId) return eventId.startsWith("event:") ? eventId : `event:${eventId}`;
|
|
19061
|
+
return void 0;
|
|
19062
|
+
}
|
|
19063
|
+
function firstString(metadata, keys) {
|
|
19064
|
+
for (const key of keys) {
|
|
19065
|
+
const value = metadata[key];
|
|
19066
|
+
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
|
19067
|
+
}
|
|
19068
|
+
return void 0;
|
|
19069
|
+
}
|
|
19070
|
+
function safeSourceRef(value) {
|
|
19071
|
+
if (!value) return void 0;
|
|
19072
|
+
const normalized = value.trim().replace(/^\[?(mem|event):/i, (_, prefix) => `${prefix.toLowerCase()}:`).replace(/\]?$/g, "");
|
|
19073
|
+
if (!normalized || /(?:password|secret|api[_-]?key|token|bearer)/i.test(normalized)) return void 0;
|
|
19074
|
+
const cleaned = normalized.replace(/[^A-Za-z0-9_.:-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 140);
|
|
19075
|
+
return cleaned || void 0;
|
|
19076
|
+
}
|
|
19077
|
+
function withSourceRefHint(compression, sourceRef) {
|
|
19078
|
+
if (!sourceRef) return compression;
|
|
19079
|
+
if (compression.metadata.mode === "off" || compression.metadata.strategy === "none") {
|
|
19080
|
+
return {
|
|
19081
|
+
text: compression.text,
|
|
19082
|
+
metadata: {
|
|
19083
|
+
...compression.metadata,
|
|
19084
|
+
sourceRef,
|
|
19085
|
+
sourceRefPreserved: false
|
|
19086
|
+
}
|
|
19087
|
+
};
|
|
19088
|
+
}
|
|
19089
|
+
const hint = `sourceRef=${sourceRef} expand=mem-source-ref`;
|
|
19090
|
+
const text = compression.text.includes(hint) ? compression.text : addSourceRefHint(compression.text, hint);
|
|
19091
|
+
const compressedChars = text.length;
|
|
19092
|
+
const savedChars = Math.max(0, compression.metadata.originalChars - compressedChars);
|
|
19093
|
+
const compressionRatio = compression.metadata.originalChars > 0 ? compressedChars / compression.metadata.originalChars : 1;
|
|
19094
|
+
return {
|
|
19095
|
+
text,
|
|
19096
|
+
metadata: {
|
|
19097
|
+
...compression.metadata,
|
|
19098
|
+
sourceRef,
|
|
19099
|
+
sourceRefPreserved: text.includes(`sourceRef=${sourceRef}`) && text.includes("expand=mem-source-ref"),
|
|
19100
|
+
compressedChars,
|
|
19101
|
+
savedChars,
|
|
19102
|
+
compressionRatio
|
|
19103
|
+
}
|
|
19104
|
+
};
|
|
19105
|
+
}
|
|
19106
|
+
function addSourceRefHint(text, hint) {
|
|
19107
|
+
if (text.startsWith("[compressed ")) {
|
|
19108
|
+
return text.replace(/^\[([^\]]+)\]/, `[$1; ${hint}]`);
|
|
19109
|
+
}
|
|
19110
|
+
return `[${hint}]
|
|
19111
|
+
${text}`;
|
|
19112
|
+
}
|
|
17950
19113
|
|
|
17951
19114
|
// src/core/task/task-matcher.ts
|
|
17952
19115
|
var DEFAULT_CONFIG7 = {
|
|
@@ -20084,7 +21247,7 @@ import * as os7 from "os";
|
|
|
20084
21247
|
import * as readline from "readline";
|
|
20085
21248
|
import { createHash as createHash9, randomUUID as randomUUID26 } from "crypto";
|
|
20086
21249
|
var CODEX_VALIDATION_DEFAULT_MAX_CONTENT_CHARS = 1e4;
|
|
20087
|
-
function
|
|
21250
|
+
function isRecord7(value) {
|
|
20088
21251
|
return typeof value === "object" && value !== null;
|
|
20089
21252
|
}
|
|
20090
21253
|
function normalizeMaybeRealpath(p) {
|
|
@@ -20100,7 +21263,7 @@ function extractCodexContentText(content, maxContentChars = CODEX_VALIDATION_DEF
|
|
|
20100
21263
|
if (content.length > 0) texts.push(content);
|
|
20101
21264
|
} else if (Array.isArray(content)) {
|
|
20102
21265
|
for (const block of content) {
|
|
20103
|
-
if (!
|
|
21266
|
+
if (!isRecord7(block)) continue;
|
|
20104
21267
|
const b = block;
|
|
20105
21268
|
const t = typeof b.type === "string" ? b.type : "";
|
|
20106
21269
|
if (t !== "input_text" && t !== "output_text" && t !== "text") continue;
|
|
@@ -20180,7 +21343,7 @@ async function readCodexSessionMeta(filePath) {
|
|
|
20180
21343
|
try {
|
|
20181
21344
|
const obj = JSON.parse(line);
|
|
20182
21345
|
if (obj.type !== "session_meta") continue;
|
|
20183
|
-
if (!
|
|
21346
|
+
if (!isRecord7(obj.payload)) break;
|
|
20184
21347
|
const payload = obj.payload;
|
|
20185
21348
|
const sessionId = typeof payload.id === "string" ? payload.id : null;
|
|
20186
21349
|
const cwd = typeof payload.cwd === "string" ? payload.cwd : null;
|
|
@@ -20310,7 +21473,7 @@ async function normalizeCodexSessionFile(filePath, options = {}) {
|
|
|
20310
21473
|
continue;
|
|
20311
21474
|
}
|
|
20312
21475
|
if (entry.type === "session_meta") continue;
|
|
20313
|
-
if (entry.type !== "response_item" || !
|
|
21476
|
+
if (entry.type !== "response_item" || !isRecord7(entry.payload)) {
|
|
20314
21477
|
summary.skippedUnsupportedRecords += 1;
|
|
20315
21478
|
continue;
|
|
20316
21479
|
}
|
|
@@ -20460,7 +21623,7 @@ var CodexSessionHistoryImporter = class {
|
|
|
20460
21623
|
try {
|
|
20461
21624
|
const obj = JSON.parse(line);
|
|
20462
21625
|
if (obj.type !== "session_meta") continue;
|
|
20463
|
-
if (!
|
|
21626
|
+
if (!isRecord7(obj.payload)) break;
|
|
20464
21627
|
const payload = obj.payload;
|
|
20465
21628
|
const sessionId = typeof payload.id === "string" ? payload.id : null;
|
|
20466
21629
|
const cwd = typeof payload.cwd === "string" ? payload.cwd : null;
|
|
@@ -20668,7 +21831,7 @@ var CodexSessionHistoryImporter = class {
|
|
|
20668
21831
|
try {
|
|
20669
21832
|
const entry = JSON.parse(line);
|
|
20670
21833
|
result2.totalMessages++;
|
|
20671
|
-
if (entry.type === "response_item" &&
|
|
21834
|
+
if (entry.type === "response_item" && isRecord7(entry.payload)) {
|
|
20672
21835
|
const payload = entry.payload;
|
|
20673
21836
|
if (payload.type !== "message") continue;
|
|
20674
21837
|
const role = typeof payload.role === "string" ? payload.role : null;
|
|
@@ -20917,6 +22080,9 @@ export {
|
|
|
20917
22080
|
RetrievalResultTypeSchema,
|
|
20918
22081
|
Retriever,
|
|
20919
22082
|
RuleBasedPerspectiveObservationExtractor,
|
|
22083
|
+
SOURCE_CAPTURE_MODES,
|
|
22084
|
+
SOURCE_PRIVACY_CLASSES,
|
|
22085
|
+
SOURCE_TRANSFORMATION_KINDS,
|
|
20920
22086
|
SQLiteEventStore,
|
|
20921
22087
|
SearchIndexItemSchema,
|
|
20922
22088
|
SessionActorRepository,
|
|
@@ -20932,6 +22098,7 @@ export {
|
|
|
20932
22098
|
SharedStoreConfigSchema,
|
|
20933
22099
|
SharedTroubleshootingEntrySchema,
|
|
20934
22100
|
SharedVectorStore,
|
|
22101
|
+
SourceContractValidationError,
|
|
20935
22102
|
SummaryDeriver,
|
|
20936
22103
|
SyncWorker,
|
|
20937
22104
|
TaskActionProjector,
|
|
@@ -20962,6 +22129,7 @@ export {
|
|
|
20962
22129
|
VectorWorkerV2,
|
|
20963
22130
|
WorkingSetItemSchema,
|
|
20964
22131
|
actionIdForTaskEntity,
|
|
22132
|
+
assertSourceAdapterContract,
|
|
20965
22133
|
backfillPerspectiveSessionActors,
|
|
20966
22134
|
buildMemoryActorId,
|
|
20967
22135
|
createCodexSessionHistoryImporter,
|
|
@@ -20989,9 +22157,14 @@ export {
|
|
|
20989
22157
|
createSharedPromoter,
|
|
20990
22158
|
createSharedStore,
|
|
20991
22159
|
createSharedVectorStore,
|
|
22160
|
+
createSourceRef,
|
|
20992
22161
|
createSummaryDeriver,
|
|
20993
22162
|
createVectorWorker,
|
|
20994
22163
|
createVectorWorkerV2,
|
|
22164
|
+
defineSourceAdapter,
|
|
22165
|
+
defineSourceSchema,
|
|
22166
|
+
defineSourceTransformation,
|
|
22167
|
+
defineSourceTransformations,
|
|
20995
22168
|
deriveCodexSessionIdFromFileName,
|
|
20996
22169
|
detectContextContentType,
|
|
20997
22170
|
emptyRetentionAuditReport,
|
|
@@ -21015,8 +22188,13 @@ export {
|
|
|
21015
22188
|
isKnownBenignTransformersWarning,
|
|
21016
22189
|
isMissingTransformersDependencyError,
|
|
21017
22190
|
isSameCanonicalKey,
|
|
22191
|
+
isSourceCaptureMode,
|
|
22192
|
+
isSourcePrivacyClass,
|
|
22193
|
+
isSourceTransformationKind,
|
|
21018
22194
|
listCodexSessionFilesRecursive,
|
|
21019
22195
|
loadSessionRegistry,
|
|
22196
|
+
looksLikeLocalAbsolutePath,
|
|
22197
|
+
looksLikePrivacySensitiveSourceValue,
|
|
21020
22198
|
makeArtifactKey,
|
|
21021
22199
|
makeCanonicalKey,
|
|
21022
22200
|
makeDedupeKey,
|
|
@@ -21052,6 +22230,13 @@ export {
|
|
|
21052
22230
|
toDisclosureResultId,
|
|
21053
22231
|
toSQLiteTimestamp,
|
|
21054
22232
|
validateCodexSessions,
|
|
22233
|
+
validateSourceAdapterCapabilities,
|
|
22234
|
+
validateSourceAdapterContract,
|
|
22235
|
+
validateSourceAdapterIdentity,
|
|
22236
|
+
validateSourceRef,
|
|
22237
|
+
validateSourceSchema,
|
|
22238
|
+
validateSourceTransformationDeclaration,
|
|
22239
|
+
validateSourceTransformationDeclarations,
|
|
21055
22240
|
withSuppressedKnownTransformersWarnings,
|
|
21056
22241
|
writeGovernanceAuditEntry
|
|
21057
22242
|
};
|