@hasna/knowledge 0.2.27 → 0.2.29

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.
Files changed (64) hide show
  1. package/README.md +140 -99
  2. package/bin/{open-knowledge-mcp.js → knowledge-mcp.js} +22 -15
  3. package/bin/{open-knowledge.js → knowledge.js} +5 -5
  4. package/dist/agent.d.ts +35 -0
  5. package/dist/artifact-store.d.ts +63 -0
  6. package/dist/auth.d.ts +35 -0
  7. package/dist/embeddings.d.ts +77 -0
  8. package/dist/index.d.ts +20 -0
  9. package/dist/index.js +5709 -0
  10. package/dist/knowledge-db.d.ts +27 -0
  11. package/dist/manifest-ingest.d.ts +35 -0
  12. package/dist/outbox-consume.d.ts +25 -0
  13. package/dist/provenance.d.ts +50 -0
  14. package/dist/providers.d.ts +89 -0
  15. package/dist/reindex.d.ts +37 -0
  16. package/dist/remote-client.d.ts +108 -0
  17. package/dist/retrieval.d.ts +71 -0
  18. package/dist/safety.d.ts +70 -0
  19. package/dist/sdk.d.ts +72 -0
  20. package/dist/search.d.ts +65 -0
  21. package/dist/service.d.ts +117 -0
  22. package/dist/source-ingest.d.ts +18 -0
  23. package/dist/source-ref.d.ts +30 -0
  24. package/dist/source-resolver.d.ts +92 -0
  25. package/dist/storage-contract.d.ts +106 -0
  26. package/dist/web-search.d.ts +40 -0
  27. package/dist/wiki-compiler.d.ts +67 -0
  28. package/dist/wiki-layout.d.ts +23 -0
  29. package/dist/workspace.d.ts +111 -0
  30. package/docs/architecture/ai-native-knowledge-base.md +16 -16
  31. package/docs/architecture/hosted-wrapper-responsibilities.md +5 -5
  32. package/docs/architecture/hybrid-semantic-search.md +12 -12
  33. package/docs/canonical-secrets-bootstrap-2026-06-08.md +1 -1
  34. package/docs/examples/company-wiki-workflow.md +19 -19
  35. package/docs/migration/json-to-sqlite.md +17 -17
  36. package/package.json +17 -10
  37. package/src/agent.ts +0 -367
  38. package/src/artifact-store.ts +0 -184
  39. package/src/auth.ts +0 -123
  40. package/src/cli.ts +0 -1184
  41. package/src/embeddings.ts +0 -516
  42. package/src/knowledge-db.ts +0 -354
  43. package/src/manifest-ingest.ts +0 -515
  44. package/src/mcp-http.js +0 -110
  45. package/src/mcp.js +0 -1503
  46. package/src/outbox-consume.ts +0 -463
  47. package/src/provenance.ts +0 -93
  48. package/src/providers.ts +0 -308
  49. package/src/reindex.ts +0 -260
  50. package/src/remote-client.ts +0 -268
  51. package/src/retrieval.ts +0 -326
  52. package/src/safety.ts +0 -265
  53. package/src/schema.js +0 -25
  54. package/src/search.ts +0 -510
  55. package/src/service.ts +0 -443
  56. package/src/source-ingest.ts +0 -268
  57. package/src/source-ref.ts +0 -104
  58. package/src/source-resolver.ts +0 -436
  59. package/src/storage-contract.ts +0 -346
  60. package/src/store.ts +0 -113
  61. package/src/web-search.ts +0 -330
  62. package/src/wiki-compiler.ts +0 -711
  63. package/src/wiki-layout.ts +0 -251
  64. package/src/workspace.ts +0 -251
package/src/safety.ts DELETED
@@ -1,265 +0,0 @@
1
- import { createHash, randomUUID } from 'node:crypto';
2
- import { relative, resolve, sep } from 'node:path';
3
- import type { Database } from 'bun:sqlite';
4
- import type { KnowledgeConfig, KnowledgeWorkspace } from './workspace';
5
-
6
- export type SafetyDecision = 'allow' | 'deny' | 'requires_approval';
7
-
8
- export interface SafetyPolicy {
9
- mode: 'local' | 'hosted';
10
- allowWriteRoots: string[];
11
- readOnlySourceAccess: boolean;
12
- network: {
13
- webSearchEnabled: boolean;
14
- s3ReadsEnabled: boolean;
15
- allowedS3Buckets: string[];
16
- };
17
- redaction: {
18
- enabled: boolean;
19
- };
20
- approvals: {
21
- generatedWritesRequireApproval: boolean;
22
- };
23
- }
24
-
25
- export interface SafetyAuditInput {
26
- event_type: string;
27
- action: string;
28
- target_uri?: string | null;
29
- decision: SafetyDecision | 'redacted' | 'info';
30
- metadata?: Record<string, unknown>;
31
- created_at?: string;
32
- }
33
-
34
- export interface RedactionFinding {
35
- type: string;
36
- severity: 'low' | 'medium' | 'high';
37
- start: number;
38
- end: number;
39
- }
40
-
41
- export interface RedactionResult {
42
- text: string;
43
- findings: RedactionFinding[];
44
- }
45
-
46
- type ConfigWithSafety = KnowledgeConfig & {
47
- safety?: {
48
- network?: {
49
- web_search_enabled?: boolean;
50
- s3_reads_enabled?: boolean;
51
- allowed_s3_buckets?: string[];
52
- };
53
- redaction?: {
54
- enabled?: boolean;
55
- };
56
- approvals?: {
57
- generated_writes_require_approval?: boolean;
58
- };
59
- };
60
- };
61
-
62
- function envEnabled(name: string): boolean {
63
- const value = process.env[name];
64
- return value === '1' || value === 'true' || value === 'yes';
65
- }
66
-
67
- export function resolveSafetyPolicy(config: KnowledgeConfig, workspace: KnowledgeWorkspace): SafetyPolicy {
68
- const extended = config as ConfigWithSafety;
69
- const configuredBuckets = new Set<string>(extended.safety?.network?.allowed_s3_buckets ?? []);
70
- if (config.storage.type === 's3' && config.storage.s3?.bucket) configuredBuckets.add(config.storage.s3.bucket);
71
- if (process.env.HASNA_KNOWLEDGE_ALLOWED_S3_BUCKETS) {
72
- for (const bucket of process.env.HASNA_KNOWLEDGE_ALLOWED_S3_BUCKETS.split(',').map((entry) => entry.trim()).filter(Boolean)) {
73
- configuredBuckets.add(bucket);
74
- }
75
- }
76
- return {
77
- mode: config.mode,
78
- allowWriteRoots: [
79
- workspace.home,
80
- workspace.artifactsDir,
81
- workspace.cacheDir,
82
- workspace.exportsDir,
83
- workspace.indexesDir,
84
- workspace.logsDir,
85
- workspace.runsDir,
86
- workspace.schemasDir,
87
- workspace.wikiDir,
88
- ].map((entry) => resolve(entry)),
89
- readOnlySourceAccess: true,
90
- network: {
91
- webSearchEnabled: extended.safety?.network?.web_search_enabled ?? envEnabled('HASNA_KNOWLEDGE_WEB_SEARCH'),
92
- s3ReadsEnabled: extended.safety?.network?.s3_reads_enabled ?? envEnabled('HASNA_KNOWLEDGE_ALLOW_S3_READS'),
93
- allowedS3Buckets: [...configuredBuckets].sort(),
94
- },
95
- redaction: {
96
- enabled: extended.safety?.redaction?.enabled ?? true,
97
- },
98
- approvals: {
99
- generatedWritesRequireApproval: extended.safety?.approvals?.generated_writes_require_approval ?? true,
100
- },
101
- };
102
- }
103
-
104
- function isInside(root: string, target: string): boolean {
105
- const rel = relative(root, target);
106
- return rel === '' || (!rel.startsWith('..') && rel !== '..' && !rel.startsWith(`..${sep}`));
107
- }
108
-
109
- export function assertWriteAllowed(targetPath: string, policy: SafetyPolicy): void {
110
- const resolved = resolve(targetPath);
111
- if (!policy.allowWriteRoots.some((root) => isInside(root, resolved))) {
112
- throw new Error(`Safety policy denied write outside .hasna/apps/knowledge: ${targetPath}`);
113
- }
114
- }
115
-
116
- export function assertS3ReadAllowed(uri: string, policy: SafetyPolicy): void {
117
- const parsed = new URL(uri);
118
- const bucket = parsed.hostname;
119
- if (!policy.network.s3ReadsEnabled) {
120
- throw new Error('Safety policy denied S3 read. Set safety.network.s3_reads_enabled=true or HASNA_KNOWLEDGE_ALLOW_S3_READS=1.');
121
- }
122
- if (!policy.network.allowedS3Buckets.includes(bucket)) {
123
- throw new Error(`Safety policy denied S3 bucket "${bucket}". Add it to safety.network.allowed_s3_buckets or HASNA_KNOWLEDGE_ALLOWED_S3_BUCKETS.`);
124
- }
125
- }
126
-
127
- export function assertWebSearchAllowed(policy: SafetyPolicy): void {
128
- if (!policy.network.webSearchEnabled) {
129
- throw new Error('Safety policy denied web search. Set safety.network.web_search_enabled=true or HASNA_KNOWLEDGE_WEB_SEARCH=1.');
130
- }
131
- }
132
-
133
- const REDACTION_PATTERNS: Array<{ type: string; severity: RedactionFinding['severity']; regex: RegExp; replacement: string }> = [
134
- { type: 'private_key_block', severity: 'high', regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, replacement: '[REDACTED:private_key_block]' },
135
- { type: 'secret_assignment', severity: 'high', regex: /\b(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"]?[^'"\s]{8,}/gi, replacement: '[REDACTED:secret_assignment]' },
136
- { type: 'openai_api_key', severity: 'high', regex: /\bsk-[A-Za-z0-9_-]{20,}\b/g, replacement: '[REDACTED:openai_api_key]' },
137
- { type: 'anthropic_api_key', severity: 'high', regex: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g, replacement: '[REDACTED:anthropic_api_key]' },
138
- { type: 'aws_access_key_id', severity: 'high', regex: /\bA(?:KIA|SIA)[A-Z0-9]{16}\b/g, replacement: '[REDACTED:aws_access_key_id]' },
139
- ];
140
-
141
- export function redactSecrets(text: string, policy?: Pick<SafetyPolicy, 'redaction'>): RedactionResult {
142
- if (policy && !policy.redaction.enabled) return { text, findings: [] };
143
- let output = text;
144
- const findings: RedactionFinding[] = [];
145
- for (const pattern of REDACTION_PATTERNS) {
146
- output = output.replace(pattern.regex, (match, ...args) => {
147
- const offset = typeof args.at(-2) === 'number' ? args.at(-2) as number : output.indexOf(match);
148
- findings.push({
149
- type: pattern.type,
150
- severity: pattern.severity,
151
- start: Math.max(0, offset),
152
- end: Math.max(0, offset + match.length),
153
- });
154
- return pattern.replacement;
155
- });
156
- }
157
- return { text: output, findings };
158
- }
159
-
160
- export function auditId(input: SafetyAuditInput): string {
161
- return `audit_${createHash('sha256')
162
- .update(`${input.event_type}\u0000${input.action}\u0000${input.target_uri ?? ''}\u0000${input.created_at ?? ''}\u0000${JSON.stringify(input.metadata ?? {})}\u0000${randomUUID()}`)
163
- .digest('hex')
164
- .slice(0, 24)}`;
165
- }
166
-
167
- export function recordAuditEvent(db: Database, input: SafetyAuditInput): string {
168
- const createdAt = input.created_at ?? new Date().toISOString();
169
- const id = auditId({ ...input, created_at: createdAt });
170
- db.run(
171
- `INSERT INTO audit_events (id, event_type, action, target_uri, decision, metadata_json, created_at)
172
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
173
- [
174
- id,
175
- input.event_type,
176
- input.action,
177
- input.target_uri ?? null,
178
- input.decision,
179
- JSON.stringify(input.metadata ?? {}),
180
- createdAt,
181
- ],
182
- );
183
- return id;
184
- }
185
-
186
- export function recordRedactionFindings(db: Database, input: {
187
- source_uri?: string | null;
188
- run_id?: string | null;
189
- findings: RedactionFinding[];
190
- metadata?: Record<string, unknown>;
191
- created_at?: string;
192
- }): number {
193
- const createdAt = input.created_at ?? new Date().toISOString();
194
- for (const finding of input.findings) {
195
- db.run(
196
- `INSERT INTO redaction_findings (id, source_uri, run_id, severity, finding_type, metadata_json, created_at)
197
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
198
- [
199
- `redact_${randomUUID()}`,
200
- input.source_uri ?? null,
201
- input.run_id ?? null,
202
- finding.severity,
203
- finding.type,
204
- JSON.stringify({ ...(input.metadata ?? {}), start: finding.start, end: finding.end }),
205
- createdAt,
206
- ],
207
- );
208
- }
209
- return input.findings.length;
210
- }
211
-
212
- export function createApprovalGate(db: Database, input: {
213
- action: string;
214
- target_uri?: string | null;
215
- reason?: string | null;
216
- approved_by?: string | null;
217
- metadata?: Record<string, unknown>;
218
- created_at?: string;
219
- }): { id: string; status: 'approved' } {
220
- const now = input.created_at ?? new Date().toISOString();
221
- const id = `approval_${randomUUID()}`;
222
- db.run(
223
- `INSERT INTO approval_gates (id, action, target_uri, status, reason, approved_by, metadata_json, created_at, updated_at)
224
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
225
- [
226
- id,
227
- input.action,
228
- input.target_uri ?? null,
229
- 'approved',
230
- input.reason ?? null,
231
- input.approved_by ?? 'local-cli',
232
- JSON.stringify(input.metadata ?? {}),
233
- now,
234
- now,
235
- ],
236
- );
237
- return { id, status: 'approved' };
238
- }
239
-
240
- export function hasApproval(db: Database, action: string, targetUri?: string | null): boolean {
241
- const row = db.query<{ id: string }, [string, string | null, string | null]>(
242
- `SELECT id FROM approval_gates
243
- WHERE action = ? AND status = 'approved' AND (target_uri IS NULL OR target_uri = ? OR ? IS NULL)
244
- ORDER BY updated_at DESC LIMIT 1`,
245
- ).get(action, targetUri ?? null, targetUri ?? null);
246
- return Boolean(row);
247
- }
248
-
249
- export function approvalStatus(db: Database, policy: SafetyPolicy, action: string, targetUri?: string | null): {
250
- action: string;
251
- target_uri: string | null;
252
- approval_required: boolean;
253
- approved: boolean;
254
- decision: SafetyDecision;
255
- } {
256
- const approvalRequired = action === 'generated_write' && policy.approvals.generatedWritesRequireApproval;
257
- const approved = !approvalRequired || hasApproval(db, action, targetUri);
258
- return {
259
- action,
260
- target_uri: targetUri ?? null,
261
- approval_required: approvalRequired,
262
- approved,
263
- decision: approved ? 'allow' : 'requires_approval',
264
- };
265
- }
package/src/schema.js DELETED
@@ -1,25 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- export const itemSchema = z.object({
4
- id: z.string().min(1),
5
- short_id: z.string().nullable().optional(),
6
- title: z.string().min(1),
7
- content: z.string(),
8
- tags: z.array(z.string()).default([]),
9
- metadata: z.record(z.string(), z.unknown()).default({}),
10
- archived: z.boolean().default(false),
11
- created_at: z.string(),
12
- updated_at: z.string(),
13
- });
14
-
15
- export const storeSchema = z.object({
16
- items: z.array(itemSchema.passthrough()).default([]),
17
- });
18
-
19
- export function validateItem(data) {
20
- return itemSchema.parse(data);
21
- }
22
-
23
- export function validateStore(data) {
24
- return storeSchema.parse(data);
25
- }