@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/knowledge",
3
- "version": "0.2.26",
3
+ "version": "0.2.27",
4
4
  "description": "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
5
5
  "type": "module",
6
6
  "bin": {
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 hosted mode
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
  };
@@ -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
  }