@hasna/knowledge 0.2.26 → 0.2.27
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 +20 -0
- package/bin/open-knowledge-mcp.js +71 -3
- package/bin/open-knowledge.js +86 -86
- package/docs/architecture/ai-native-knowledge-base.md +24 -0
- package/docs/architecture/hosted-wrapper-responsibilities.md +8 -0
- package/docs/canonical-secrets-bootstrap-2026-06-08.md +127 -0
- package/package.json +1 -1
- package/src/cli.ts +8 -5
- package/src/service.ts +13 -2
- package/src/storage-contract.ts +54 -1
- package/src/workspace.ts +38 -0
|
@@ -159,6 +159,30 @@ s3://<knowledge-bucket>/<org>/<project>/knowledge/
|
|
|
159
159
|
wiki/
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
+
Hasna XYZ production uses the canonical open-source knowledge bucket and app
|
|
163
|
+
path-compatible prefix:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
s3://hasna-xyz-opensource-knowledge-prod/.hasna/apps/knowledge/
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The app config can be materialized with:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
open-knowledge setup --mode hosted --canonical-hasna-xyz --scope project --json
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The canonical metadata-only secret paths are:
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
hasna/xyz/opensource/knowledge/prod/env
|
|
179
|
+
hasna/xyz/opensource/knowledge/prod/aws
|
|
180
|
+
hasna/xyz/opensource/knowledge/prod/s3
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`hasna/xyz/opensource/knowledge/prod/rds` is reserved for a future hosted
|
|
184
|
+
runtime database if the wrapper provisions one.
|
|
185
|
+
|
|
162
186
|
Raw files still route through `open-files`. Knowledge S3 storage is for derived
|
|
163
187
|
artifacts such as wiki pages, index shards, schema versions, logs, exports, and
|
|
164
188
|
run outputs.
|
|
@@ -86,6 +86,14 @@ The OSS package may know a storage contract, bucket name, prefix, region, and
|
|
|
86
86
|
profile. It must not contain tenant secret values, connector credentials, RDS
|
|
87
87
|
passwords, hosted KMS key material, or privileged AWS role assumptions.
|
|
88
88
|
|
|
89
|
+
For Hasna XYZ production, the OSS contract names
|
|
90
|
+
`hasna-xyz-opensource-knowledge-prod` and prefix
|
|
91
|
+
`.hasna/apps/knowledge/`, plus metadata-only secret paths under
|
|
92
|
+
`hasna/xyz/opensource/knowledge/prod/{env,aws,s3}`. Hosted code is responsible
|
|
93
|
+
for resolving those secrets, assuming AWS roles, enforcing tenant prefixes, and
|
|
94
|
+
provisioning `hasna/xyz/opensource/knowledge/prod/rds` only if a hosted runtime
|
|
95
|
+
database is introduced.
|
|
96
|
+
|
|
89
97
|
Generated artifacts are safe to sync remotely only when they remain derived:
|
|
90
98
|
wiki pages, index shards, schema files, logs, exports, run payloads, embeddings,
|
|
91
99
|
and citation metadata. Raw source bytes stay in `open-files`.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Canonical Secrets Bootstrap Evidence: 2026-06-08
|
|
2
|
+
|
|
3
|
+
Scope: canonical Hasna XYZ app secret paths in account `hasna-xyz-infra`
|
|
4
|
+
(`789877399345`), region `us-east-1`, mirrored into the local `spark02`
|
|
5
|
+
`secrets` vault.
|
|
6
|
+
|
|
7
|
+
This note records names and migration intent only. It intentionally does not
|
|
8
|
+
include secret payloads, passwords, API keys, connection strings, or `.env`
|
|
9
|
+
contents.
|
|
10
|
+
|
|
11
|
+
## Ownership Rule
|
|
12
|
+
|
|
13
|
+
App-owned runtime/config secrets use:
|
|
14
|
+
|
|
15
|
+
```txt
|
|
16
|
+
hasna/xyz/{app_type}/{app}/prod/{component}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Shared infra/admin pointers use an infra-owned path:
|
|
20
|
+
|
|
21
|
+
```txt
|
|
22
|
+
hasna/xyz/infra/{resource_group}/prod/{component}/{role}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Legacy master credentials copied for migration are suffixed with
|
|
26
|
+
`legacy-master`. They are deprecation inputs for migration jobs, not the clean
|
|
27
|
+
runtime secret names new app code should read.
|
|
28
|
+
|
|
29
|
+
## Open Files
|
|
30
|
+
|
|
31
|
+
Created or verified in AWS Secrets Manager and the local vault:
|
|
32
|
+
|
|
33
|
+
```txt
|
|
34
|
+
hasna/xyz/opensource/files/prod/env
|
|
35
|
+
hasna/xyz/opensource/files/prod/aws
|
|
36
|
+
hasna/xyz/opensource/files/prod/s3
|
|
37
|
+
hasna/xyz/opensource/files/prod/rds
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Meaning:
|
|
41
|
+
|
|
42
|
+
- `env`: app environment configuration.
|
|
43
|
+
- `aws`: AWS account/profile/region and related app config metadata.
|
|
44
|
+
- `s3`: canonical app storage bucket metadata for
|
|
45
|
+
`hasna-xyz-opensource-files-prod`.
|
|
46
|
+
- `rds`: app runtime database connection fields for database `files` and role
|
|
47
|
+
`files_app`.
|
|
48
|
+
|
|
49
|
+
The `aws` and `s3` entries are metadata-only. They do not contain access keys or
|
|
50
|
+
tokens.
|
|
51
|
+
|
|
52
|
+
## Open Knowledge
|
|
53
|
+
|
|
54
|
+
Created or verified in AWS Secrets Manager and the local vault:
|
|
55
|
+
|
|
56
|
+
```txt
|
|
57
|
+
hasna/xyz/opensource/knowledge/prod/env
|
|
58
|
+
hasna/xyz/opensource/knowledge/prod/aws
|
|
59
|
+
hasna/xyz/opensource/knowledge/prod/s3
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Meaning:
|
|
63
|
+
|
|
64
|
+
- `env`: open-knowledge production app config metadata.
|
|
65
|
+
- `aws`: AWS account/profile/region and related app config metadata.
|
|
66
|
+
- `s3`: canonical app storage bucket metadata for
|
|
67
|
+
`hasna-xyz-opensource-knowledge-prod`.
|
|
68
|
+
|
|
69
|
+
No `hasna/xyz/opensource/knowledge/prod/rds` secret was created in this pass.
|
|
70
|
+
The OSS package is local-first and currently uses SQLite for local knowledge
|
|
71
|
+
state. If a hosted wrapper later provisions an app database, the app-owned
|
|
72
|
+
runtime secret should use:
|
|
73
|
+
|
|
74
|
+
```txt
|
|
75
|
+
hasna/xyz/opensource/knowledge/prod/rds
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Legacy Secret Mapping
|
|
79
|
+
|
|
80
|
+
Mapped legacy AWS Secrets Manager names to canonical ownership paths:
|
|
81
|
+
|
|
82
|
+
| Legacy name | Canonical path | Use |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `prod/microservice/rds/master` | `hasna/xyz/opensource/microservices/prod/rds/legacy-master` | Migration-only legacy master alias. |
|
|
85
|
+
| `prod/connect/rds/master` | `hasna/xyz/opensource/connectors/prod/rds/legacy-master` | Migration-only legacy master alias. |
|
|
86
|
+
| `internalapps/prod/rds/master` | `hasna/xyz/infra/apps/prod/postgres/legacy-internalapps-master` | Migration-only legacy shared/admin alias. |
|
|
87
|
+
| `internalapps/prod/iapp-news/env` | `hasna/xyz/internalapp/news/prod/env` | Canonical app env path for internalapp `news`. |
|
|
88
|
+
|
|
89
|
+
The three RDS aliases preserve old master credential payloads under explicit
|
|
90
|
+
legacy names so migration jobs can read them without relying on noncanonical
|
|
91
|
+
paths. They should not be used as the final runtime credentials for new app
|
|
92
|
+
code. Clean app runtime database credentials should be provisioned under
|
|
93
|
+
app-owned paths such as:
|
|
94
|
+
|
|
95
|
+
```txt
|
|
96
|
+
hasna/xyz/opensource/files/prod/rds
|
|
97
|
+
hasna/xyz/internalapp/news/prod/rds
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The shared canonical Postgres admin pointer remains:
|
|
101
|
+
|
|
102
|
+
```txt
|
|
103
|
+
hasna/xyz/infra/apps/prod/postgres/master
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Verification
|
|
107
|
+
|
|
108
|
+
AWS Secrets Manager name-only verification returned these canonical entries:
|
|
109
|
+
|
|
110
|
+
```txt
|
|
111
|
+
hasna/xyz/infra/apps/prod/postgres/legacy-internalapps-master
|
|
112
|
+
hasna/xyz/infra/apps/prod/postgres/master
|
|
113
|
+
hasna/xyz/internalapp/news/prod/env
|
|
114
|
+
hasna/xyz/opensource/connectors/prod/rds/legacy-master
|
|
115
|
+
hasna/xyz/opensource/files/prod/aws
|
|
116
|
+
hasna/xyz/opensource/files/prod/env
|
|
117
|
+
hasna/xyz/opensource/files/prod/rds
|
|
118
|
+
hasna/xyz/opensource/files/prod/s3
|
|
119
|
+
hasna/xyz/opensource/knowledge/prod/aws
|
|
120
|
+
hasna/xyz/opensource/knowledge/prod/env
|
|
121
|
+
hasna/xyz/opensource/knowledge/prod/s3
|
|
122
|
+
hasna/xyz/opensource/microservices/prod/rds/legacy-master
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Local `secrets list` verification returned the same names with redacted values.
|
|
126
|
+
|
|
127
|
+
No secret values were printed during creation or verification.
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -57,6 +57,7 @@ interface Flags {
|
|
|
57
57
|
provider?: string;
|
|
58
58
|
mode?: string;
|
|
59
59
|
apiUrl?: string;
|
|
60
|
+
canonicalHasnaXyz?: boolean;
|
|
60
61
|
apiKey?: string;
|
|
61
62
|
email?: string;
|
|
62
63
|
org?: string;
|
|
@@ -125,6 +126,7 @@ function parseArgs(argv: string[]): ParseResult {
|
|
|
125
126
|
case '--provider': flags.provider = argv[i + 1]; i += 1; break;
|
|
126
127
|
case '--mode': flags.mode = argv[i + 1]; i += 1; break;
|
|
127
128
|
case '--api-url': flags.apiUrl = argv[i + 1]; i += 1; break;
|
|
129
|
+
case '--canonical-hasna-xyz': flags.canonicalHasnaXyz = true; break;
|
|
128
130
|
case '--api-key': flags.apiKey = argv[i + 1]; i += 1; break;
|
|
129
131
|
case '--email': flags.email = argv[i + 1]; i += 1; break;
|
|
130
132
|
case '--org': flags.org = argv[i + 1]; i += 1; break;
|
|
@@ -204,7 +206,7 @@ Commands:
|
|
|
204
206
|
dedupe Remove duplicate items by title+content (requires --yes)
|
|
205
207
|
stats Show knowledge base statistics
|
|
206
208
|
paths Show resolved workspace/store paths
|
|
207
|
-
setup Configure local or
|
|
209
|
+
setup Configure local, hosted, or canonical Hasna XYZ S3 mode
|
|
208
210
|
auth login|whoami|logout Manage hosted API credentials
|
|
209
211
|
remote contracts|status Inspect hosted client contracts/readiness
|
|
210
212
|
storage status|validate Inspect local/S3 artifact storage contract
|
|
@@ -299,7 +301,7 @@ function printCommandHelp(command: string): void {
|
|
|
299
301
|
if (command === 'dedupe') { console.log('Usage: open-knowledge dedupe --yes [--json]'); return; }
|
|
300
302
|
if (command === 'stats') { console.log('Usage: open-knowledge stats [--json]'); return; }
|
|
301
303
|
if (command === 'paths') { console.log('Usage: open-knowledge paths [--scope local|global|project] [--json]'); return; }
|
|
302
|
-
if (command === 'setup') { console.log('Usage: open-knowledge setup --mode local|hosted [--api-url https://...] [--scope local|global|project] [--json]'); return; }
|
|
304
|
+
if (command === 'setup') { console.log('Usage: open-knowledge setup --mode local|hosted [--api-url https://...] [--canonical-hasna-xyz] [--scope local|global|project] [--json]'); return; }
|
|
303
305
|
if (command === 'auth') { console.log('Usage: open-knowledge auth login|whoami|logout [--api-key <key>] [--email <email>] [--org <slug>] [--api-url https://...] [--scope local|global|project] [--json]'); return; }
|
|
304
306
|
if (command === 'remote') { console.log('Usage: open-knowledge remote contracts|status [--scope local|global|project] [--json]'); return; }
|
|
305
307
|
if (command === 'storage') { console.log('Usage: open-knowledge storage status|validate [--scope local|global|project] [--json]'); return; }
|
|
@@ -358,11 +360,11 @@ async function run(argv: string[]): Promise<void> {
|
|
|
358
360
|
if (flags.completions) {
|
|
359
361
|
const shell = flags.completions;
|
|
360
362
|
if (shell === 'bash') {
|
|
361
|
-
console.log(`_open_knowledge() { local cur; cur="${"$"}{COMP_WORDS[COMP_CWORD]}"; COMPREPLY=($(compgen -W "add list get update archive restore upsert untag delete export prune dedupe stats paths setup auth remote storage db wiki source ingest reindex search web ask build embeddings providers safety help ls rm edit unarchive knowledge --json --yes --help --version --desc --page --limit --search --sort --id --store --title --content --url --tag --format --completions --purpose --model --dimensions --semantic --context --generate --approve-write --provider --mode --api-url --api-key --email --org --org-id --user-id --domain --file-results --full --fake --no-color --scope --archived --include-archived" -- "$cur")); }; complete -F _open_knowledge open-knowledge`);
|
|
363
|
+
console.log(`_open_knowledge() { local cur; cur="${"$"}{COMP_WORDS[COMP_CWORD]}"; COMPREPLY=($(compgen -W "add list get update archive restore upsert untag delete export prune dedupe stats paths setup auth remote storage db wiki source ingest reindex search web ask build embeddings providers safety help ls rm edit unarchive knowledge --json --yes --help --version --desc --page --limit --search --sort --id --store --title --content --url --tag --format --completions --purpose --model --dimensions --semantic --context --generate --approve-write --provider --mode --api-url --canonical-hasna-xyz --api-key --email --org --org-id --user-id --domain --file-results --full --fake --no-color --scope --archived --include-archived" -- "$cur")); }; complete -F _open_knowledge open-knowledge`);
|
|
362
364
|
} else if (shell === 'zsh') {
|
|
363
|
-
console.log(`#compdef open-knowledge\n_open_knowledge() { _arguments -C "1: :(add list get update archive restore upsert untag delete export prune dedupe stats paths setup auth remote storage db wiki source ingest reindex search web ask build embeddings providers safety help ls rm edit unarchive knowledge)" "(--json)--json" "(--yes)-y" "(--help)--help" "(--version)--version" "(--desc)--desc" "(--archived)--archived" "(--include-archived)--include-archived" "(--semantic)--semantic" "(--context)--context" "(--generate)--generate" "(--approve-write)--approve-write" "(--file-results)--file-results" "(--full)--full" "(--fake)--fake" "(-p --page)"{-p,--page}"[page number]:number:" "(-l --limit)"{-l,--limit}"[items per page]:number:" "(-s --search)"{-s,--search}"[search text]:text:" "(--sort)--sort"\{created,title\}:" "(--id)--id[item id]:id:" "(--store)--store[store path]:path:" "(--title)--title[new title]:" "(--content)--content[new content]:" "(--url)--url[source url]:" "(-t --tag)"{-t,--tag}"[tag]:tag:" "(--format)--format[json|jsonl]:" "(--completions)--completions[output completions]:shell:(bash zsh fish):" "(--purpose)--purpose[purpose]:" "(--model)--model[model ref]:" "(--dimensions)--dimensions[embedding dimensions]:number:" "(--provider)--provider[provider]:" "(--mode)--mode"\{local,hosted\}:" "(--api-url)--api-url[hosted API URL]:" "(--api-key)--api-key[hosted API key]:" "(--email)--email[email]:" "(--org)--org[org slug]:" "(--org-id)--org-id[org id]:" "(--user-id)--user-id[user id]:" "(--domain)--domain[domain]:" "(--no-color)--no-color[disable color]" "(--scope)--scope"\{local,global,project\}:" }; _open_knowledge`);
|
|
365
|
+
console.log(`#compdef open-knowledge\n_open_knowledge() { _arguments -C "1: :(add list get update archive restore upsert untag delete export prune dedupe stats paths setup auth remote storage db wiki source ingest reindex search web ask build embeddings providers safety help ls rm edit unarchive knowledge)" "(--json)--json" "(--yes)-y" "(--help)--help" "(--version)--version" "(--desc)--desc" "(--archived)--archived" "(--include-archived)--include-archived" "(--semantic)--semantic" "(--context)--context" "(--generate)--generate" "(--approve-write)--approve-write" "(--canonical-hasna-xyz)--canonical-hasna-xyz" "(--file-results)--file-results" "(--full)--full" "(--fake)--fake" "(-p --page)"{-p,--page}"[page number]:number:" "(-l --limit)"{-l,--limit}"[items per page]:number:" "(-s --search)"{-s,--search}"[search text]:text:" "(--sort)--sort"\{created,title\}:" "(--id)--id[item id]:id:" "(--store)--store[store path]:path:" "(--title)--title[new title]:" "(--content)--content[new content]:" "(--url)--url[source url]:" "(-t --tag)"{-t,--tag}"[tag]:tag:" "(--format)--format[json|jsonl]:" "(--completions)--completions[output completions]:shell:(bash zsh fish):" "(--purpose)--purpose[purpose]:" "(--model)--model[model ref]:" "(--dimensions)--dimensions[embedding dimensions]:number:" "(--provider)--provider[provider]:" "(--mode)--mode"\{local,hosted\}:" "(--api-url)--api-url[hosted API URL]:" "(--api-key)--api-key[hosted API key]:" "(--email)--email[email]:" "(--org)--org[org slug]:" "(--org-id)--org-id[org id]:" "(--user-id)--user-id[user id]:" "(--domain)--domain[domain]:" "(--no-color)--no-color[disable color]" "(--scope)--scope"\{local,global,project\}:" }; _open_knowledge`);
|
|
364
366
|
} else if (shell === 'fish') {
|
|
365
|
-
console.log(`complete -c open-knowledge -f; complete -c open-knowledge -a "add list get update archive restore upsert untag delete export prune dedupe stats paths setup auth remote storage db wiki source ingest reindex search web ask build embeddings providers safety help ls rm edit unarchive knowledge"; complete -c open-knowledge -l json; complete -c open-knowledge -l yes -s y; complete -c open-knowledge -l help -s h; complete -c open-knowledge -l version -s v; complete -c open-knowledge -l desc; complete -c open-knowledge -l archived; complete -c open-knowledge -l include-archived; complete -c open-knowledge -l semantic; complete -c open-knowledge -l context; complete -c open-knowledge -l generate; complete -c open-knowledge -l approve-write; complete -c open-knowledge -l provider; complete -c open-knowledge -l mode; complete -c open-knowledge -l api-url; complete -c open-knowledge -l api-key; complete -c open-knowledge -l email; complete -c open-knowledge -l org; complete -c open-knowledge -l org-id; complete -c open-knowledge -l user-id; complete -c open-knowledge -l domain; complete -c open-knowledge -l file-results; complete -c open-knowledge -l full; complete -c open-knowledge -l fake; complete -c open-knowledge -s p -l page; complete -c open-knowledge -s l -l limit; complete -c open-knowledge -s s -l search; complete -c open-knowledge -l sort; complete -c open-knowledge -l id; complete -c open-knowledge -l store; complete -c open-knowledge -l title; complete -c open-knowledge -l content; complete -c open-knowledge -l url; complete -c open-knowledge -s t -l tag; complete -c open-knowledge -l format; complete -c open-knowledge -l completions; complete -c open-knowledge -l purpose; complete -c open-knowledge -l model; complete -c open-knowledge -l dimensions; complete -c open-knowledge -l no-color; complete -c open-knowledge -l scope -a "local global project"`);
|
|
367
|
+
console.log(`complete -c open-knowledge -f; complete -c open-knowledge -a "add list get update archive restore upsert untag delete export prune dedupe stats paths setup auth remote storage db wiki source ingest reindex search web ask build embeddings providers safety help ls rm edit unarchive knowledge"; complete -c open-knowledge -l json; complete -c open-knowledge -l yes -s y; complete -c open-knowledge -l help -s h; complete -c open-knowledge -l version -s v; complete -c open-knowledge -l desc; complete -c open-knowledge -l archived; complete -c open-knowledge -l include-archived; complete -c open-knowledge -l semantic; complete -c open-knowledge -l context; complete -c open-knowledge -l generate; complete -c open-knowledge -l approve-write; complete -c open-knowledge -l canonical-hasna-xyz; complete -c open-knowledge -l provider; complete -c open-knowledge -l mode; complete -c open-knowledge -l api-url; complete -c open-knowledge -l api-key; complete -c open-knowledge -l email; complete -c open-knowledge -l org; complete -c open-knowledge -l org-id; complete -c open-knowledge -l user-id; complete -c open-knowledge -l domain; complete -c open-knowledge -l file-results; complete -c open-knowledge -l full; complete -c open-knowledge -l fake; complete -c open-knowledge -s p -l page; complete -c open-knowledge -s l -l limit; complete -c open-knowledge -s s -l search; complete -c open-knowledge -l sort; complete -c open-knowledge -l id; complete -c open-knowledge -l store; complete -c open-knowledge -l title; complete -c open-knowledge -l content; complete -c open-knowledge -l url; complete -c open-knowledge -s t -l tag; complete -c open-knowledge -l format; complete -c open-knowledge -l completions; complete -c open-knowledge -l purpose; complete -c open-knowledge -l model; complete -c open-knowledge -l dimensions; complete -c open-knowledge -l no-color; complete -c open-knowledge -l scope -a "local global project"`);
|
|
366
368
|
} else {
|
|
367
369
|
throw new Error("Invalid --completions value. Use 'bash', 'zsh', or 'fish'.");
|
|
368
370
|
}
|
|
@@ -397,6 +399,7 @@ async function run(argv: string[]): Promise<void> {
|
|
|
397
399
|
const result = service.setup({
|
|
398
400
|
mode: flags.mode,
|
|
399
401
|
apiUrl: flags.apiUrl,
|
|
402
|
+
canonicalHasnaXyz: flags.canonicalHasnaXyz,
|
|
400
403
|
});
|
|
401
404
|
output(result, flags.json);
|
|
402
405
|
return;
|
package/src/service.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
} from './storage-contract';
|
|
37
37
|
import { initializeWikiLayout, recordWikiLayoutCatalog } from './wiki-layout';
|
|
38
38
|
import {
|
|
39
|
+
canonicalHasnaXyzKnowledgeStorage,
|
|
39
40
|
ensureKnowledgeWorkspace,
|
|
40
41
|
readKnowledgeConfig,
|
|
41
42
|
resolveScopedWorkspace,
|
|
@@ -70,6 +71,9 @@ export interface KnowledgeSetupResult {
|
|
|
70
71
|
ok: true;
|
|
71
72
|
mode: KnowledgeConfig['mode'];
|
|
72
73
|
api_url: string | null;
|
|
74
|
+
storage_type: KnowledgeConfig['storage']['type'];
|
|
75
|
+
artifact_uri_prefix: string;
|
|
76
|
+
canonical_hasna_xyz: StorageContract['canonical_hasna_xyz'];
|
|
73
77
|
config_path: string;
|
|
74
78
|
next: string[];
|
|
75
79
|
message: string;
|
|
@@ -130,7 +134,7 @@ export class KnowledgeService {
|
|
|
130
134
|
return validateStorageConfig(this.config(), this.ensureWorkspace());
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
setup(options: { mode?: string; apiUrl?: string } = {}): KnowledgeSetupResult {
|
|
137
|
+
setup(options: { mode?: string; apiUrl?: string; canonicalHasnaXyz?: boolean } = {}): KnowledgeSetupResult {
|
|
134
138
|
const workspace = this.ensureWorkspace();
|
|
135
139
|
const current = this.config();
|
|
136
140
|
const mode = normalizeMode(options.mode) ?? current.mode;
|
|
@@ -146,16 +150,23 @@ export class KnowledgeService {
|
|
|
146
150
|
...(current.hosted ?? {}),
|
|
147
151
|
...(apiUrl ? { api_url: apiUrl } : {}),
|
|
148
152
|
},
|
|
153
|
+
storage: options.canonicalHasnaXyz
|
|
154
|
+
? canonicalHasnaXyzKnowledgeStorage()
|
|
155
|
+
: current.storage,
|
|
149
156
|
};
|
|
150
157
|
writeKnowledgeConfig(workspace.configPath, nextConfig);
|
|
151
158
|
this.cachedConfig = nextConfig;
|
|
159
|
+
const storage = resolveStorageContract(nextConfig, workspace, this.scope);
|
|
152
160
|
return {
|
|
153
161
|
ok: true,
|
|
154
162
|
mode,
|
|
155
163
|
api_url: nextConfig.hosted?.api_url ?? null,
|
|
164
|
+
storage_type: nextConfig.storage.type,
|
|
165
|
+
artifact_uri_prefix: storage.artifact_store.uri_prefix,
|
|
166
|
+
canonical_hasna_xyz: storage.canonical_hasna_xyz,
|
|
156
167
|
config_path: workspace.configPath,
|
|
157
168
|
next: mode === 'hosted'
|
|
158
|
-
? ['open-knowledge auth login --api-key <key>', 'open-knowledge remote contracts --json']
|
|
169
|
+
? ['open-knowledge auth login --api-key <key>', 'open-knowledge storage status --json', 'open-knowledge remote contracts --json']
|
|
159
170
|
: ['open-knowledge search <query>', 'knowledge <prompt>'],
|
|
160
171
|
message: `Set knowledge mode to ${mode}`,
|
|
161
172
|
};
|
package/src/storage-contract.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Database } from 'bun:sqlite';
|
|
|
3
3
|
import { DEFAULT_KNOWLEDGE_API_URL, normalizeKnowledgeApiOrigin } from './auth';
|
|
4
4
|
import { REMOTE_KNOWLEDGE_CONTRACT_VERSION } from './remote-client';
|
|
5
5
|
import type { KnowledgeConfig, KnowledgeWorkspace } from './workspace';
|
|
6
|
-
import { HASNA_KNOWLEDGE_APP_PATH } from './workspace';
|
|
6
|
+
import { HASNA_KNOWLEDGE_APP_PATH, HASNA_XYZ_KNOWLEDGE_CANONICAL } from './workspace';
|
|
7
7
|
|
|
8
8
|
export interface StorageArtifactClass {
|
|
9
9
|
kind: string;
|
|
@@ -36,6 +36,30 @@ export interface StorageContract {
|
|
|
36
36
|
kms_key_configured: boolean;
|
|
37
37
|
} | null;
|
|
38
38
|
};
|
|
39
|
+
canonical_hasna_xyz: {
|
|
40
|
+
division: typeof HASNA_XYZ_KNOWLEDGE_CANONICAL.division;
|
|
41
|
+
app_type: typeof HASNA_XYZ_KNOWLEDGE_CANONICAL.app_type;
|
|
42
|
+
app: typeof HASNA_XYZ_KNOWLEDGE_CANONICAL.app;
|
|
43
|
+
env: typeof HASNA_XYZ_KNOWLEDGE_CANONICAL.env;
|
|
44
|
+
active: boolean;
|
|
45
|
+
local_path: string;
|
|
46
|
+
s3: {
|
|
47
|
+
bucket: string;
|
|
48
|
+
region: string;
|
|
49
|
+
profile: string;
|
|
50
|
+
prefix: string;
|
|
51
|
+
uri_prefix: string;
|
|
52
|
+
server_side_encryption: string;
|
|
53
|
+
};
|
|
54
|
+
secrets: {
|
|
55
|
+
env: string;
|
|
56
|
+
aws: string;
|
|
57
|
+
s3: string;
|
|
58
|
+
rds: null;
|
|
59
|
+
future_rds: string;
|
|
60
|
+
};
|
|
61
|
+
evidence_doc: string;
|
|
62
|
+
};
|
|
39
63
|
hosted: {
|
|
40
64
|
enabled: boolean;
|
|
41
65
|
api_url: string;
|
|
@@ -134,6 +158,11 @@ export function resolveStorageContract(
|
|
|
134
158
|
const s3 = config.storage.s3 ?? null;
|
|
135
159
|
const prefix = s3?.prefix?.replace(/^\/+|\/+$/g, '') ?? '';
|
|
136
160
|
const s3UriPrefix = s3 ? `s3://${s3.bucket}/${prefix ? `${prefix}/` : ''}` : '';
|
|
161
|
+
const canonicalPrefix = HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.prefix.replace(/^\/+|\/+$/g, '');
|
|
162
|
+
const canonicalS3UriPrefix = `s3://${HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.bucket}/${canonicalPrefix}/`;
|
|
163
|
+
const canonicalActive = config.storage.type === 's3'
|
|
164
|
+
&& s3?.bucket === HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.bucket
|
|
165
|
+
&& (s3.region ?? null) === HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.region;
|
|
137
166
|
|
|
138
167
|
return {
|
|
139
168
|
scope,
|
|
@@ -171,6 +200,30 @@ export function resolveStorageContract(
|
|
|
171
200
|
}
|
|
172
201
|
: null,
|
|
173
202
|
},
|
|
203
|
+
canonical_hasna_xyz: {
|
|
204
|
+
division: HASNA_XYZ_KNOWLEDGE_CANONICAL.division,
|
|
205
|
+
app_type: HASNA_XYZ_KNOWLEDGE_CANONICAL.app_type,
|
|
206
|
+
app: HASNA_XYZ_KNOWLEDGE_CANONICAL.app,
|
|
207
|
+
env: HASNA_XYZ_KNOWLEDGE_CANONICAL.env,
|
|
208
|
+
active: canonicalActive,
|
|
209
|
+
local_path: HASNA_XYZ_KNOWLEDGE_CANONICAL.local_path,
|
|
210
|
+
s3: {
|
|
211
|
+
bucket: HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.bucket,
|
|
212
|
+
region: HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.region,
|
|
213
|
+
profile: HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.profile,
|
|
214
|
+
prefix: canonicalPrefix,
|
|
215
|
+
uri_prefix: canonicalS3UriPrefix,
|
|
216
|
+
server_side_encryption: HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.server_side_encryption,
|
|
217
|
+
},
|
|
218
|
+
secrets: {
|
|
219
|
+
env: HASNA_XYZ_KNOWLEDGE_CANONICAL.secrets.env,
|
|
220
|
+
aws: HASNA_XYZ_KNOWLEDGE_CANONICAL.secrets.aws,
|
|
221
|
+
s3: HASNA_XYZ_KNOWLEDGE_CANONICAL.secrets.s3,
|
|
222
|
+
rds: HASNA_XYZ_KNOWLEDGE_CANONICAL.secrets.rds,
|
|
223
|
+
future_rds: HASNA_XYZ_KNOWLEDGE_CANONICAL.secrets.future_rds,
|
|
224
|
+
},
|
|
225
|
+
evidence_doc: HASNA_XYZ_KNOWLEDGE_CANONICAL.evidence_doc,
|
|
226
|
+
},
|
|
174
227
|
hosted: {
|
|
175
228
|
enabled: config.mode === 'hosted',
|
|
176
229
|
api_url: normalizeKnowledgeApiOrigin(config.hosted?.api_url ?? DEFAULT_KNOWLEDGE_API_URL),
|
package/src/workspace.ts
CHANGED
|
@@ -82,6 +82,44 @@ export interface KnowledgeConfig {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export const HASNA_XYZ_KNOWLEDGE_CANONICAL = {
|
|
86
|
+
division: 'xyz',
|
|
87
|
+
app_type: 'opensource',
|
|
88
|
+
app: 'knowledge',
|
|
89
|
+
env: 'prod',
|
|
90
|
+
local_path: HASNA_KNOWLEDGE_APP_PATH,
|
|
91
|
+
s3: {
|
|
92
|
+
bucket: 'hasna-xyz-opensource-knowledge-prod',
|
|
93
|
+
region: 'us-east-1',
|
|
94
|
+
profile: 'hasna-xyz-infra',
|
|
95
|
+
prefix: '.hasna/apps/knowledge',
|
|
96
|
+
server_side_encryption: 'AES256',
|
|
97
|
+
},
|
|
98
|
+
secrets: {
|
|
99
|
+
env: 'hasna/xyz/opensource/knowledge/prod/env',
|
|
100
|
+
aws: 'hasna/xyz/opensource/knowledge/prod/aws',
|
|
101
|
+
s3: 'hasna/xyz/opensource/knowledge/prod/s3',
|
|
102
|
+
rds: null,
|
|
103
|
+
future_rds: 'hasna/xyz/opensource/knowledge/prod/rds',
|
|
104
|
+
},
|
|
105
|
+
source_owner: 'open-files',
|
|
106
|
+
evidence_doc: 'docs/canonical-secrets-bootstrap-2026-06-08.md',
|
|
107
|
+
} as const;
|
|
108
|
+
|
|
109
|
+
export function canonicalHasnaXyzKnowledgeStorage(): KnowledgeConfig['storage'] {
|
|
110
|
+
return {
|
|
111
|
+
type: 's3',
|
|
112
|
+
artifacts_root: 'artifacts',
|
|
113
|
+
s3: {
|
|
114
|
+
bucket: HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.bucket,
|
|
115
|
+
prefix: HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.prefix,
|
|
116
|
+
region: HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.region,
|
|
117
|
+
profile: HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.profile,
|
|
118
|
+
server_side_encryption: HASNA_XYZ_KNOWLEDGE_CANONICAL.s3.server_side_encryption,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
85
123
|
export function legacyGlobalStorePath(): string {
|
|
86
124
|
return join(homedir(), '.open-knowledge', 'db.json');
|
|
87
125
|
}
|