@oscharko-dev/keiko-evidence 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -0
- package/dist/aggregate.d.ts +4 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +21 -0
- package/dist/build.d.ts +3 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +227 -0
- package/dist/connected-context-evidence.d.ts +47 -0
- package/dist/connected-context-evidence.d.ts.map +1 -0
- package/dist/connected-context-evidence.js +197 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +4 -0
- package/dist/index-api.d.ts +15 -0
- package/dist/index-api.d.ts.map +1 -0
- package/dist/index-api.js +136 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/persist.d.ts +9 -0
- package/dist/persist.d.ts.map +1 -0
- package/dist/persist.js +40 -0
- package/dist/promptEnhancement/index.d.ts +7 -0
- package/dist/promptEnhancement/index.d.ts.map +1 -0
- package/dist/promptEnhancement/index.js +10 -0
- package/dist/promptEnhancement/manifestSchema.d.ts +71 -0
- package/dist/promptEnhancement/manifestSchema.d.ts.map +1 -0
- package/dist/promptEnhancement/manifestSchema.js +307 -0
- package/dist/promptEnhancement/redaction.d.ts +17 -0
- package/dist/promptEnhancement/redaction.d.ts.map +1 -0
- package/dist/promptEnhancement/redaction.js +66 -0
- package/dist/promptEnhancement/store.d.ts +64 -0
- package/dist/promptEnhancement/store.d.ts.map +1 -0
- package/dist/promptEnhancement/store.js +409 -0
- package/dist/qualityIntelligence/candidatesArtifact.d.ts +74 -0
- package/dist/qualityIntelligence/candidatesArtifact.d.ts.map +1 -0
- package/dist/qualityIntelligence/candidatesArtifact.js +258 -0
- package/dist/qualityIntelligence/companionStore.d.ts +37 -0
- package/dist/qualityIntelligence/companionStore.d.ts.map +1 -0
- package/dist/qualityIntelligence/companionStore.js +158 -0
- package/dist/qualityIntelligence/figmaSnapshot/schema.d.ts +123 -0
- package/dist/qualityIntelligence/figmaSnapshot/schema.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshot/schema.js +163 -0
- package/dist/qualityIntelligence/figmaSnapshot/store.d.ts +144 -0
- package/dist/qualityIntelligence/figmaSnapshot/store.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshot/store.js +898 -0
- package/dist/qualityIntelligence/index.d.ts +18 -0
- package/dist/qualityIntelligence/index.d.ts.map +1 -0
- package/dist/qualityIntelligence/index.js +21 -0
- package/dist/qualityIntelligence/manifestSchema.d.ts +154 -0
- package/dist/qualityIntelligence/manifestSchema.d.ts.map +1 -0
- package/dist/qualityIntelligence/manifestSchema.js +70 -0
- package/dist/qualityIntelligence/redaction.d.ts +10 -0
- package/dist/qualityIntelligence/redaction.d.ts.map +1 -0
- package/dist/qualityIntelligence/redaction.js +103 -0
- package/dist/qualityIntelligence/retention.d.ts +71 -0
- package/dist/qualityIntelligence/retention.d.ts.map +1 -0
- package/dist/qualityIntelligence/retention.js +287 -0
- package/dist/qualityIntelligence/retentionPolicy.d.ts +10 -0
- package/dist/qualityIntelligence/retentionPolicy.d.ts.map +1 -0
- package/dist/qualityIntelligence/retentionPolicy.js +38 -0
- package/dist/qualityIntelligence/store.d.ts +95 -0
- package/dist/qualityIntelligence/store.d.ts.map +1 -0
- package/dist/qualityIntelligence/store.js +483 -0
- package/dist/redaction.d.ts +2 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +4 -0
- package/dist/report.d.ts +17 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +50 -0
- package/dist/retention.d.ts +4 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +95 -0
- package/dist/runid.d.ts +2 -0
- package/dist/runid.d.ts.map +1 -0
- package/dist/runid.js +4 -0
- package/dist/side-file.d.ts +9 -0
- package/dist/side-file.d.ts.map +1 -0
- package/dist/side-file.js +102 -0
- package/dist/store.d.ts +8 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +332 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -0
- package/dist/workflow-evidence.d.ts +36 -0
- package/dist/workflow-evidence.d.ts.map +1 -0
- package/dist/workflow-evidence.js +158 -0
- package/package.json +32 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// Prompt Enhancement evidence manifest shape (Epic #1307, Issue #1313; ADR-0044 §1/§5).
|
|
2
|
+
//
|
|
3
|
+
// Versioned, persistable record the Prompt Enhancer writes through `keiko-evidence` after a run.
|
|
4
|
+
// Mirrors the Quality Intelligence evidence template (record → redact → hash → validate → write) but
|
|
5
|
+
// for the enhancer domain. It captures exactly what AC4 requires — the original input, the enhanced
|
|
6
|
+
// output, the applied rules, the assumptions, the candidate scores, the model metadata when
|
|
7
|
+
// applicable, and the verification status — and NOTHING that could leak a secret: the original input is
|
|
8
|
+
// a SHA-256 fingerprint plus a redacted, truncated excerpt; every other free-text field is passed
|
|
9
|
+
// through the security redactor before it reaches this shape.
|
|
10
|
+
//
|
|
11
|
+
// Schema evolution follows the EVIDENCE_SCHEMA_VERSION rule: a breaking structural change introduces a
|
|
12
|
+
// NEW `peEvidenceSchemaVersion` literal member rather than mutating the current literal, so persisted artefacts stay
|
|
13
|
+
// discriminable across versions.
|
|
14
|
+
import { GROUNDING_DIRECTIVES, LEAST_PRIVILEGE_CONSTRAINTS, PROMPT_SAFETY_DECISIONS, PROMPT_SAFETY_VERIFICATION_STATUSES, isPromptSafetyViolationCode, } from "@oscharko-dev/keiko-contracts";
|
|
15
|
+
export const PROMPT_ENHANCEMENT_EVIDENCE_SCHEMA_VERSION = 2;
|
|
16
|
+
const ALLOWED_STATUSES = new Set([
|
|
17
|
+
"validated",
|
|
18
|
+
"requires-human-review",
|
|
19
|
+
"rejected",
|
|
20
|
+
]);
|
|
21
|
+
// ─── Strict-schema gate ──────────────────────────────────────────────────────────────
|
|
22
|
+
// The closed set of allowed top-level keys. A persisted record carrying any extra key fails the gate
|
|
23
|
+
// on read, matching the EvidenceManifest discipline. Update in lock-step with the interface above.
|
|
24
|
+
const ALLOWED_TOP_LEVEL_KEYS = new Set([
|
|
25
|
+
"peEvidenceSchemaVersion",
|
|
26
|
+
"runId",
|
|
27
|
+
"recordedAt",
|
|
28
|
+
"requestId",
|
|
29
|
+
"status",
|
|
30
|
+
"inputRedactedFingerprintSha256",
|
|
31
|
+
"inputExcerptRedacted",
|
|
32
|
+
"enhancedPromptId",
|
|
33
|
+
"enhancedPromptTextRedacted",
|
|
34
|
+
"appliedSafetyRules",
|
|
35
|
+
"appliedGroundingDirectives",
|
|
36
|
+
"assumptions",
|
|
37
|
+
"candidateScores",
|
|
38
|
+
"safety",
|
|
39
|
+
"modelMetadata",
|
|
40
|
+
"redactionSummary",
|
|
41
|
+
"integrityHashes",
|
|
42
|
+
"totals",
|
|
43
|
+
]);
|
|
44
|
+
const HEX_SHA256 = /^[0-9a-f]{64}$/u;
|
|
45
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
46
|
+
const isNonNegativeInteger = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
47
|
+
const isUnitInterval = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
48
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
49
|
+
const isUniqueStringArray = (value, allowed) => isStringArray(value) &&
|
|
50
|
+
value.every((entry) => allowed.includes(entry)) &&
|
|
51
|
+
new Set(value).size === value.length;
|
|
52
|
+
const isGroundingDirectiveArray = (value) => isUniqueStringArray(value, GROUNDING_DIRECTIVES);
|
|
53
|
+
const isLeastPrivilegeArray = (value) => isUniqueStringArray(value, LEAST_PRIVILEGE_CONSTRAINTS);
|
|
54
|
+
function validateRedactionSummary(value) {
|
|
55
|
+
if (!isRecord(value))
|
|
56
|
+
return "redactionSummary must be an object";
|
|
57
|
+
if (!isNonNegativeInteger(value.totalStringsScanned)) {
|
|
58
|
+
return "redactionSummary.totalStringsScanned must be a non-negative integer";
|
|
59
|
+
}
|
|
60
|
+
if (!isNonNegativeInteger(value.stringsRedacted)) {
|
|
61
|
+
return "redactionSummary.stringsRedacted must be a non-negative integer";
|
|
62
|
+
}
|
|
63
|
+
if (!isRecord(value.patternsMatched)) {
|
|
64
|
+
return "redactionSummary.patternsMatched must be an object";
|
|
65
|
+
}
|
|
66
|
+
if (Object.values(value.patternsMatched).some((count) => !isNonNegativeInteger(count))) {
|
|
67
|
+
return "redactionSummary.patternsMatched values must be non-negative integers";
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
function validateIntegrityHashes(value) {
|
|
72
|
+
if (!isRecord(value))
|
|
73
|
+
return "integrityHashes must be an object";
|
|
74
|
+
for (const key of ["enhancedOutput", "appliedRules", "candidateScores", "record"]) {
|
|
75
|
+
if (typeof value[key] !== "string" || !HEX_SHA256.test(value[key])) {
|
|
76
|
+
return `integrityHashes.${key} must be a SHA-256 hex digest`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
function validateCandidateScoreRow(row, index) {
|
|
82
|
+
const label = `candidateScores[${String(index)}]`;
|
|
83
|
+
if (!isRecord(row))
|
|
84
|
+
return `${label} must be an object`;
|
|
85
|
+
if (typeof row.candidateId !== "string" || row.candidateId.length === 0) {
|
|
86
|
+
return `${label}.candidateId must be a non-empty string`;
|
|
87
|
+
}
|
|
88
|
+
if (typeof row.profile !== "string" || row.profile.length === 0) {
|
|
89
|
+
return `${label}.profile must be a non-empty string`;
|
|
90
|
+
}
|
|
91
|
+
if (!isUnitInterval(row.aggregateScore)) {
|
|
92
|
+
return `${label}.aggregateScore must be a number in [0, 1]`;
|
|
93
|
+
}
|
|
94
|
+
if (!isNonNegativeInteger(row.estimatedTokens)) {
|
|
95
|
+
return `${label}.estimatedTokens must be a non-negative integer`;
|
|
96
|
+
}
|
|
97
|
+
if (typeof row.selected !== "boolean") {
|
|
98
|
+
return `${label}.selected must be a boolean`;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
function validateCandidateScoreRows(value) {
|
|
103
|
+
if (!Array.isArray(value))
|
|
104
|
+
return "candidateScores must be an array";
|
|
105
|
+
for (const [index, row] of value.entries()) {
|
|
106
|
+
const error = validateCandidateScoreRow(row, index);
|
|
107
|
+
if (error !== undefined)
|
|
108
|
+
return error;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
function validateSafetyRecordScalars(value) {
|
|
113
|
+
if (typeof value.decision !== "string" ||
|
|
114
|
+
!PROMPT_SAFETY_DECISIONS.includes(value.decision)) {
|
|
115
|
+
return "safety.decision must be a known safety decision";
|
|
116
|
+
}
|
|
117
|
+
if (typeof value.verificationStatus !== "string" ||
|
|
118
|
+
!PROMPT_SAFETY_VERIFICATION_STATUSES.includes(value.verificationStatus)) {
|
|
119
|
+
return "safety.verificationStatus must be a known verification status";
|
|
120
|
+
}
|
|
121
|
+
if (typeof value.requiresHumanReview !== "boolean") {
|
|
122
|
+
return "safety.requiresHumanReview must be a boolean";
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
function validateSafetyRecordLists(value) {
|
|
127
|
+
if (!Array.isArray(value.findingCodes) ||
|
|
128
|
+
value.findingCodes.some((code) => !isPromptSafetyViolationCode(code))) {
|
|
129
|
+
return "safety.findingCodes must be an array of known violation codes";
|
|
130
|
+
}
|
|
131
|
+
if (!isLeastPrivilegeArray(value.leastPrivilege)) {
|
|
132
|
+
return "safety.leastPrivilege must be an array of unique least-privilege constraints";
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
function validateAcceptedSafetyStatus(value) {
|
|
137
|
+
if (value.decision !== "accepted" ||
|
|
138
|
+
value.verificationStatus !== "passed" ||
|
|
139
|
+
value.requiresHumanReview) {
|
|
140
|
+
return "status validated must match an accepted safety record";
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
function validateReviewSafetyStatus(value) {
|
|
145
|
+
const leastPrivilege = value.leastPrivilege;
|
|
146
|
+
if (value.decision !== "requires-human-review" ||
|
|
147
|
+
value.verificationStatus !== "passed-with-review" ||
|
|
148
|
+
!value.requiresHumanReview ||
|
|
149
|
+
!leastPrivilege.includes("require-human-approval")) {
|
|
150
|
+
return "status requires-human-review must match a review-gated safety record";
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
function validateRejectedSafetyStatus(value) {
|
|
155
|
+
const findingCodes = value.findingCodes;
|
|
156
|
+
if (value.decision !== "rejected" ||
|
|
157
|
+
value.verificationStatus !== "failed" ||
|
|
158
|
+
findingCodes.length === 0) {
|
|
159
|
+
return "status rejected must match a failed safety record with findings";
|
|
160
|
+
}
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
function validateSafetyStatusConsistency(value, status) {
|
|
164
|
+
if (status === "validated")
|
|
165
|
+
return validateAcceptedSafetyStatus(value);
|
|
166
|
+
if (status === "requires-human-review")
|
|
167
|
+
return validateReviewSafetyStatus(value);
|
|
168
|
+
return validateRejectedSafetyStatus(value);
|
|
169
|
+
}
|
|
170
|
+
function validateSafetyRecord(value, status) {
|
|
171
|
+
if (!isRecord(value))
|
|
172
|
+
return "safety must be an object";
|
|
173
|
+
return (validateSafetyRecordScalars(value) ??
|
|
174
|
+
validateSafetyRecordLists(value) ??
|
|
175
|
+
validateSafetyStatusConsistency(value, status));
|
|
176
|
+
}
|
|
177
|
+
function validateModelMetadata(value) {
|
|
178
|
+
if (!isRecord(value))
|
|
179
|
+
return "modelMetadata must be an object";
|
|
180
|
+
if (typeof value.deterministic !== "boolean") {
|
|
181
|
+
return "modelMetadata.deterministic must be a boolean";
|
|
182
|
+
}
|
|
183
|
+
if (value.modelId !== undefined && typeof value.modelId !== "string") {
|
|
184
|
+
return "modelMetadata.modelId must be a string when present";
|
|
185
|
+
}
|
|
186
|
+
if (value.profile !== undefined && typeof value.profile !== "string") {
|
|
187
|
+
return "modelMetadata.profile must be a string when present";
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
function validateTotals(value) {
|
|
192
|
+
if (!isRecord(value))
|
|
193
|
+
return "totals must be an object";
|
|
194
|
+
for (const key of [
|
|
195
|
+
"candidateScores",
|
|
196
|
+
"appliedSafetyRules",
|
|
197
|
+
"assumptions",
|
|
198
|
+
"safetyFindings",
|
|
199
|
+
]) {
|
|
200
|
+
if (!isNonNegativeInteger(value[key])) {
|
|
201
|
+
return `totals.${key} must be a non-negative integer`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
const schemaError = (reason) => ({
|
|
207
|
+
ok: false,
|
|
208
|
+
reason,
|
|
209
|
+
});
|
|
210
|
+
function validateManifestIdentityFields(record) {
|
|
211
|
+
if (typeof record.runId !== "string" || record.runId.length === 0) {
|
|
212
|
+
return "runId must be a non-empty string";
|
|
213
|
+
}
|
|
214
|
+
if (typeof record.recordedAt !== "string" || Number.isNaN(Date.parse(record.recordedAt))) {
|
|
215
|
+
return "recordedAt must be an ISO timestamp string";
|
|
216
|
+
}
|
|
217
|
+
if (typeof record.requestId !== "string" || record.requestId.length === 0) {
|
|
218
|
+
return "requestId must be a non-empty string";
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
function validateManifestTextFields(record) {
|
|
223
|
+
if (typeof record.inputRedactedFingerprintSha256 !== "string" ||
|
|
224
|
+
!HEX_SHA256.test(record.inputRedactedFingerprintSha256)) {
|
|
225
|
+
return "inputRedactedFingerprintSha256 must be a SHA-256 hex digest";
|
|
226
|
+
}
|
|
227
|
+
for (const key of [
|
|
228
|
+
"inputExcerptRedacted",
|
|
229
|
+
"enhancedPromptId",
|
|
230
|
+
"enhancedPromptTextRedacted",
|
|
231
|
+
]) {
|
|
232
|
+
if (typeof record[key] !== "string") {
|
|
233
|
+
return `${key} must be a string`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
function validateManifestCollectionFields(record) {
|
|
239
|
+
if (!isStringArray(record.appliedSafetyRules)) {
|
|
240
|
+
return "appliedSafetyRules must be an array of strings";
|
|
241
|
+
}
|
|
242
|
+
if (!isGroundingDirectiveArray(record.appliedGroundingDirectives)) {
|
|
243
|
+
return "appliedGroundingDirectives must be an array of unique grounding directives";
|
|
244
|
+
}
|
|
245
|
+
if (!isStringArray(record.assumptions)) {
|
|
246
|
+
return "assumptions must be an array of strings";
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
function validateManifestNestedFields(record, status) {
|
|
251
|
+
const candidateError = validateCandidateScoreRows(record.candidateScores);
|
|
252
|
+
if (candidateError !== undefined)
|
|
253
|
+
return candidateError;
|
|
254
|
+
const safetyError = validateSafetyRecord(record.safety, status);
|
|
255
|
+
if (safetyError !== undefined)
|
|
256
|
+
return safetyError;
|
|
257
|
+
const modelMetadataError = validateModelMetadata(record.modelMetadata);
|
|
258
|
+
if (modelMetadataError !== undefined)
|
|
259
|
+
return modelMetadataError;
|
|
260
|
+
const redactionError = validateRedactionSummary(record.redactionSummary);
|
|
261
|
+
if (redactionError !== undefined)
|
|
262
|
+
return redactionError;
|
|
263
|
+
const integrityError = validateIntegrityHashes(record.integrityHashes);
|
|
264
|
+
if (integrityError !== undefined)
|
|
265
|
+
return integrityError;
|
|
266
|
+
const totalsError = validateTotals(record.totals);
|
|
267
|
+
if (totalsError !== undefined)
|
|
268
|
+
return totalsError;
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
function validateManifestFields(record) {
|
|
272
|
+
const status = record.status;
|
|
273
|
+
const error = validateManifestIdentityFields(record) ??
|
|
274
|
+
validateManifestTextFields(record) ??
|
|
275
|
+
validateManifestCollectionFields(record) ??
|
|
276
|
+
validateManifestNestedFields(record, status);
|
|
277
|
+
if (error !== undefined)
|
|
278
|
+
return schemaError(error);
|
|
279
|
+
return { ok: true, reason: undefined };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Strict-schema gate for a deserialised Prompt Enhancement evidence record. Validates the
|
|
283
|
+
* schema-version literal, the closed set of top-level keys, and the status enum. Counts/integrity
|
|
284
|
+
* correctness is orthogonally enforced by the builder before persist and re-checked on read by the
|
|
285
|
+
* store.
|
|
286
|
+
*/
|
|
287
|
+
export function validatePromptEnhancementEvidenceManifest(value) {
|
|
288
|
+
if (typeof value !== "object" || value === null) {
|
|
289
|
+
return { ok: false, reason: "manifest is not an object" };
|
|
290
|
+
}
|
|
291
|
+
const record = value;
|
|
292
|
+
if (record.peEvidenceSchemaVersion !== PROMPT_ENHANCEMENT_EVIDENCE_SCHEMA_VERSION) {
|
|
293
|
+
return {
|
|
294
|
+
ok: false,
|
|
295
|
+
reason: `unexpected peEvidenceSchemaVersion (expected ${String(PROMPT_ENHANCEMENT_EVIDENCE_SCHEMA_VERSION)})`,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
for (const key of Object.keys(record)) {
|
|
299
|
+
if (!ALLOWED_TOP_LEVEL_KEYS.has(key)) {
|
|
300
|
+
return { ok: false, reason: `unknown manifest key: ${key}` };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (typeof record.status !== "string" || !ALLOWED_STATUSES.has(record.status)) {
|
|
304
|
+
return { ok: false, reason: "invalid status" };
|
|
305
|
+
}
|
|
306
|
+
return validateManifestFields(record);
|
|
307
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PromptEnhancementRedactionSummary } from "./manifestSchema.js";
|
|
2
|
+
export interface PromptEnhancementRedactionOptions {
|
|
3
|
+
readonly additionalSecrets?: readonly string[] | undefined;
|
|
4
|
+
readonly redactAllStrings?: boolean | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface PromptEnhancementRedactionResult<TValue> {
|
|
7
|
+
readonly redacted: TValue;
|
|
8
|
+
readonly summary: PromptEnhancementRedactionSummary;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Redact every string leaf of a plain-JSON Prompt Enhancement evidence value. Idempotent: scanning
|
|
12
|
+
* already-redacted text matches no pattern (the `[REDACTED]` marker carries no credential shape), so
|
|
13
|
+
* re-running this over its own output is a no-op modulo the counters. The generic `TValue` is a
|
|
14
|
+
* phantom: callers pass an object shape and get the same shape back (JSON has no class identity).
|
|
15
|
+
*/
|
|
16
|
+
export declare function redactPromptEnhancementEvidence<TValue>(value: TValue, options?: PromptEnhancementRedactionOptions): PromptEnhancementRedactionResult<TValue>;
|
|
17
|
+
//# sourceMappingURL=redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../src/promptEnhancement/redaction.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,qBAAqB,CAAC;AA+D7E,MAAM,WAAW,iCAAiC;IAIhD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAG3D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjD;AAED,MAAM,WAAW,gCAAgC,CAAC,MAAM;IACtD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,iCAAiC,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EACpD,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,iCAAsC,GAC9C,gCAAgC,CAAC,MAAM,CAAC,CAW1C"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Prompt Enhancement redaction wrapper (Issue #1313, ADR-0044 §5).
|
|
2
|
+
//
|
|
3
|
+
// Runs the authoritative `redact` from `@oscharko-dev/keiko-security` over every string leaf of a
|
|
4
|
+
// candidate Prompt Enhancement evidence record, returning the redacted value plus a counts-only
|
|
5
|
+
// `redactionSummary` (matched-string count, never the matched text). Topology literals (baseUrl/proxy
|
|
6
|
+
// values) are passed through as `additionalSecrets` for substring redaction. When opaque credentials
|
|
7
|
+
// (API keys/tokens) are configured, callers set `redactAllStrings`; every string leaf is replaced by
|
|
8
|
+
// a fixed marker before evidence hashing, so credential values never travel toward hash inputs.
|
|
9
|
+
import { redact } from "@oscharko-dev/keiko-security";
|
|
10
|
+
const REDACTED = "[REDACTED]";
|
|
11
|
+
function createCounter() {
|
|
12
|
+
return {
|
|
13
|
+
totalStringsScanned: 0,
|
|
14
|
+
stringsRedacted: 0,
|
|
15
|
+
patternsMatched: { "security-package": 0, "opaque-secret": 0 },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function redactString(input, additionalSecrets, redactAllStrings, counter) {
|
|
19
|
+
counter.totalStringsScanned += 1;
|
|
20
|
+
if (redactAllStrings) {
|
|
21
|
+
counter.patternsMatched["opaque-secret"] = (counter.patternsMatched["opaque-secret"] ?? 0) + 1;
|
|
22
|
+
counter.stringsRedacted += 1;
|
|
23
|
+
return REDACTED;
|
|
24
|
+
}
|
|
25
|
+
const redacted = redact(input, additionalSecrets);
|
|
26
|
+
if (redacted !== input) {
|
|
27
|
+
counter.patternsMatched["security-package"] =
|
|
28
|
+
(counter.patternsMatched["security-package"] ?? 0) + 1;
|
|
29
|
+
counter.stringsRedacted += 1;
|
|
30
|
+
}
|
|
31
|
+
return redacted;
|
|
32
|
+
}
|
|
33
|
+
function deepRedact(value, additionalSecrets, redactAllStrings, counter) {
|
|
34
|
+
if (typeof value === "string") {
|
|
35
|
+
return redactString(value, additionalSecrets, redactAllStrings, counter);
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return value.map((item) => deepRedact(item, additionalSecrets, redactAllStrings, counter));
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "object" && value !== null) {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [key, child] of Object.entries(value)) {
|
|
43
|
+
out[key] = deepRedact(child, additionalSecrets, redactAllStrings, counter);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Redact every string leaf of a plain-JSON Prompt Enhancement evidence value. Idempotent: scanning
|
|
51
|
+
* already-redacted text matches no pattern (the `[REDACTED]` marker carries no credential shape), so
|
|
52
|
+
* re-running this over its own output is a no-op modulo the counters. The generic `TValue` is a
|
|
53
|
+
* phantom: callers pass an object shape and get the same shape back (JSON has no class identity).
|
|
54
|
+
*/
|
|
55
|
+
export function redactPromptEnhancementEvidence(value, options = {}) {
|
|
56
|
+
const additionalSecrets = options.additionalSecrets ?? [];
|
|
57
|
+
const redactAllStrings = options.redactAllStrings ?? false;
|
|
58
|
+
const counter = createCounter();
|
|
59
|
+
const redacted = deepRedact(value, additionalSecrets, redactAllStrings, counter);
|
|
60
|
+
const summary = {
|
|
61
|
+
totalStringsScanned: counter.totalStringsScanned,
|
|
62
|
+
stringsRedacted: counter.stringsRedacted,
|
|
63
|
+
patternsMatched: { ...counter.patternsMatched },
|
|
64
|
+
};
|
|
65
|
+
return { redacted, summary };
|
|
66
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type WorkspaceFs } from "@oscharko-dev/keiko-workspace";
|
|
2
|
+
import type { GroundingDirective } from "@oscharko-dev/keiko-contracts";
|
|
3
|
+
import { type PromptEnhancementCandidateScoreRow, type PromptEnhancementEvidenceManifest, type PromptEnhancementEvidenceStatus, type PromptEnhancementModelMetadata, type PromptEnhancementSafetyRecord } from "./manifestSchema.js";
|
|
4
|
+
import { type PromptEnhancementRedactionOptions } from "./redaction.js";
|
|
5
|
+
export declare const PE_SUBDIR = "pe";
|
|
6
|
+
export interface PromptEnhancementRecordInput {
|
|
7
|
+
readonly runId: string;
|
|
8
|
+
readonly recordedAt: string;
|
|
9
|
+
readonly requestId: string;
|
|
10
|
+
readonly status: PromptEnhancementEvidenceStatus;
|
|
11
|
+
readonly originalInput: string;
|
|
12
|
+
readonly inputExcerptMaxChars?: number | undefined;
|
|
13
|
+
readonly enhancedPromptId: string;
|
|
14
|
+
readonly enhancedPromptText: string;
|
|
15
|
+
readonly appliedSafetyRules: readonly string[];
|
|
16
|
+
readonly appliedGroundingDirectives: readonly GroundingDirective[];
|
|
17
|
+
readonly assumptions: readonly string[];
|
|
18
|
+
readonly candidateScores: readonly PromptEnhancementCandidateScoreRow[];
|
|
19
|
+
readonly safety: PromptEnhancementSafetyRecord;
|
|
20
|
+
readonly modelMetadata: PromptEnhancementModelMetadata;
|
|
21
|
+
}
|
|
22
|
+
export interface PromptEnhancementRecordOptions {
|
|
23
|
+
readonly store?: PromptEnhancementLocalStore | undefined;
|
|
24
|
+
readonly evidenceDir?: string | undefined;
|
|
25
|
+
readonly redaction?: PromptEnhancementRedactionOptions | undefined;
|
|
26
|
+
}
|
|
27
|
+
export interface PromptEnhancementRecordResult {
|
|
28
|
+
readonly manifest: PromptEnhancementEvidenceManifest;
|
|
29
|
+
readonly location: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function buildPromptEnhancementEvidenceManifest(input: PromptEnhancementRecordInput, redaction?: PromptEnhancementRedactionOptions): {
|
|
32
|
+
readonly manifest: PromptEnhancementEvidenceManifest;
|
|
33
|
+
};
|
|
34
|
+
export interface PromptEnhancementLocalStore {
|
|
35
|
+
readonly record: (manifest: PromptEnhancementEvidenceManifest) => string;
|
|
36
|
+
readonly load: (runId: string) => PromptEnhancementEvidenceManifest | undefined;
|
|
37
|
+
readonly list: () => readonly string[];
|
|
38
|
+
readonly location: (runId: string) => string;
|
|
39
|
+
readonly delete: (runId: string) => boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function createInMemoryPromptEnhancementLocalStore(): PromptEnhancementLocalStore;
|
|
42
|
+
export interface PromptEnhancementNodeStoreOptions {
|
|
43
|
+
readonly fs?: WorkspaceFs;
|
|
44
|
+
readonly randomSuffix?: () => string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build a Prompt Enhancement store that writes under `<evidenceDir>/pe/`. The caller passes the SAME
|
|
48
|
+
* evidence dir it would pass to `createNodeEvidenceStore`; the store layers the `pe/` subdir itself.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createNodePromptEnhancementLocalStore(evidenceDir: string, options?: PromptEnhancementNodeStoreOptions): PromptEnhancementLocalStore;
|
|
51
|
+
/**
|
|
52
|
+
* Build and persist a Prompt Enhancement run record. Redacts every free-text leaf, hashes the redacted
|
|
53
|
+
* groups, validates the totals invariant, then writes the manifest through the resolved store. The
|
|
54
|
+
* store is supplied via `options.store` (explicit, e.g. in-memory for tests) or `options.evidenceDir`
|
|
55
|
+
* (resolve to a node adapter); one MUST be provided.
|
|
56
|
+
*/
|
|
57
|
+
export declare function recordPromptEnhancementRun(input: PromptEnhancementRecordInput, options?: PromptEnhancementRecordOptions): PromptEnhancementRecordResult;
|
|
58
|
+
export interface PromptEnhancementLoadOptions {
|
|
59
|
+
readonly store?: PromptEnhancementLocalStore | undefined;
|
|
60
|
+
readonly evidenceDir?: string | undefined;
|
|
61
|
+
}
|
|
62
|
+
export declare function loadPromptEnhancementRun(runId: string, options?: PromptEnhancementLoadOptions): PromptEnhancementEvidenceManifest | undefined;
|
|
63
|
+
export declare function listPromptEnhancementRuns(options?: PromptEnhancementLoadOptions): readonly string[];
|
|
64
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/promptEnhancement/store.ts"],"names":[],"mappings":"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"}
|