@kitsy/cnos 1.1.2 → 1.3.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.
Files changed (66) hide show
  1. package/README.md +6 -3
  2. package/dist/browser/index.cjs +94 -0
  3. package/dist/browser/index.d.cts +16 -0
  4. package/dist/browser/index.d.ts +16 -0
  5. package/dist/browser/index.js +67 -0
  6. package/dist/build/index.cjs +2889 -0
  7. package/dist/build/index.d.cts +5 -0
  8. package/dist/build/index.d.ts +5 -0
  9. package/dist/build/index.js +26 -0
  10. package/dist/{chunk-53HXUSM6.js → chunk-CDXJISGB.js} +1 -1
  11. package/dist/{chunk-33ZDYDQJ.js → chunk-DRKDNY4I.js} +1470 -462
  12. package/dist/chunk-E7SE6N26.js +189 -0
  13. package/dist/chunk-EDCLLCNL.js +200 -0
  14. package/dist/{chunk-7FBRVJD6.js → chunk-FC3IV6A7.js} +1 -31
  15. package/dist/{chunk-JQGGSNCL.js → chunk-JDII6O72.js} +1 -1
  16. package/dist/chunk-K6QYI2T4.js +105 -0
  17. package/dist/{chunk-IHSV5AFX.js → chunk-OOKFRWTN.js} +1 -1
  18. package/dist/{chunk-HOS4E7XO.js → chunk-OWUZQ4OH.js} +1 -1
  19. package/dist/{chunk-IQOUWY6T.js → chunk-QTKXPY3N.js} +1 -1
  20. package/dist/configure/index.cjs +2928 -0
  21. package/dist/configure/index.d.cts +12 -0
  22. package/dist/configure/index.d.ts +12 -0
  23. package/dist/configure/index.js +24 -0
  24. package/dist/{envNaming-BrOk5ndZ.d.cts → envNaming-D6k66myh.d.cts} +1 -1
  25. package/dist/{envNaming-DCaNdnrF.d.ts → envNaming-Dy3WYiGK.d.ts} +1 -1
  26. package/dist/index.cjs +1396 -264
  27. package/dist/index.d.cts +2 -12
  28. package/dist/index.d.ts +2 -12
  29. package/dist/index.js +13 -143
  30. package/dist/internal.cjs +1913 -63
  31. package/dist/internal.d.cts +190 -8
  32. package/dist/internal.d.ts +190 -8
  33. package/dist/internal.js +669 -3
  34. package/dist/plugin/basic-schema.cjs +29 -2
  35. package/dist/plugin/basic-schema.d.cts +1 -1
  36. package/dist/plugin/basic-schema.d.ts +1 -1
  37. package/dist/plugin/basic-schema.js +2 -2
  38. package/dist/plugin/cli-args.cjs +29 -2
  39. package/dist/plugin/cli-args.d.cts +1 -1
  40. package/dist/plugin/cli-args.d.ts +1 -1
  41. package/dist/plugin/cli-args.js +2 -2
  42. package/dist/plugin/dotenv.cjs +38 -11
  43. package/dist/plugin/dotenv.d.cts +2 -2
  44. package/dist/plugin/dotenv.d.ts +2 -2
  45. package/dist/plugin/dotenv.js +2 -2
  46. package/dist/plugin/env-export.cjs +60 -48
  47. package/dist/plugin/env-export.d.cts +2 -2
  48. package/dist/plugin/env-export.d.ts +2 -2
  49. package/dist/plugin/env-export.js +2 -2
  50. package/dist/plugin/filesystem.cjs +46 -91
  51. package/dist/plugin/filesystem.d.cts +1 -1
  52. package/dist/plugin/filesystem.d.ts +1 -1
  53. package/dist/plugin/filesystem.js +2 -2
  54. package/dist/plugin/process-env.cjs +33 -6
  55. package/dist/plugin/process-env.d.cts +2 -2
  56. package/dist/plugin/process-env.d.ts +2 -2
  57. package/dist/plugin/process-env.js +2 -2
  58. package/dist/{plugin-BVNEHj19.d.cts → plugin-CyNkf7Dm.d.cts} +42 -2
  59. package/dist/{plugin-BVNEHj19.d.ts → plugin-CyNkf7Dm.d.ts} +42 -2
  60. package/dist/runtime/index.cjs +3116 -0
  61. package/dist/runtime/index.d.cts +23 -0
  62. package/dist/runtime/index.d.ts +23 -0
  63. package/dist/runtime/index.js +15 -0
  64. package/dist/{toPublicEnv-Dd152fFy.d.cts → toPublicEnv-Cz72m6y0.d.cts} +1 -1
  65. package/dist/{toPublicEnv-Gwz3xTK0.d.ts → toPublicEnv-D2PZkaN-.d.ts} +1 -1
  66. package/package.json +26 -1
@@ -1,8 +1,3 @@
1
- // ../core/src/utils/path.ts
2
- import { access } from "fs/promises";
3
- import os from "os";
4
- import path from "path";
5
-
6
1
  // ../core/src/errors.ts
7
2
  var CnosError = class extends Error {
8
3
  constructor(message) {
@@ -17,6 +12,16 @@ var CnosManifestError = class extends CnosError {
17
12
  }
18
13
  manifestPath;
19
14
  };
15
+ var CnosSecurityError = class extends CnosError {
16
+ constructor(message) {
17
+ super(message);
18
+ }
19
+ };
20
+ var CnosAuthenticationError = class extends CnosError {
21
+ constructor(message) {
22
+ super(message);
23
+ }
24
+ };
20
25
  var CnosKeyNotFoundError = class extends CnosError {
21
26
  constructor(key) {
22
27
  super(`Missing required CNOS config key: ${key}`);
@@ -25,7 +30,149 @@ var CnosKeyNotFoundError = class extends CnosError {
25
30
  key;
26
31
  };
27
32
 
33
+ // ../core/src/runtime/inspect.ts
34
+ function inspectValue(graph, key) {
35
+ const entry = graph.entries.get(key);
36
+ if (!entry) {
37
+ throw new CnosKeyNotFoundError(key);
38
+ }
39
+ return {
40
+ key: entry.key,
41
+ value: entry.value,
42
+ namespace: entry.namespace,
43
+ profile: graph.profile,
44
+ profileSource: graph.profileSource,
45
+ workspace: {
46
+ id: graph.workspace.workspaceId,
47
+ source: graph.workspace.workspaceSource,
48
+ chain: graph.workspace.workspaceChain
49
+ },
50
+ winner: {
51
+ sourceId: entry.winner.sourceId,
52
+ pluginId: entry.winner.pluginId,
53
+ workspaceId: entry.winner.workspaceId,
54
+ ...entry.winner.origin ? { origin: entry.winner.origin } : {}
55
+ },
56
+ overridden: entry.overridden.map((override) => ({
57
+ sourceId: override.sourceId,
58
+ pluginId: override.pluginId,
59
+ workspaceId: override.workspaceId,
60
+ value: override.value,
61
+ ...override.origin ? { origin: override.origin } : {}
62
+ }))
63
+ };
64
+ }
65
+
66
+ // ../core/src/keychain/linux.ts
67
+ import { execFile, spawn } from "child_process";
68
+ import { promisify } from "util";
69
+ var execFileAsync = promisify(execFile);
70
+ async function readLinuxKeychain(entry) {
71
+ try {
72
+ const { stdout } = await execFileAsync("secret-tool", ["lookup", "service", "cnos", "account", entry]);
73
+ const value = stdout.trim();
74
+ return value.length > 0 ? value : void 0;
75
+ } catch {
76
+ return void 0;
77
+ }
78
+ }
79
+ async function writeLinuxKeychain(entry, value) {
80
+ await new Promise((resolve, reject) => {
81
+ const child = spawn("secret-tool", ["store", "--label", `CNOS ${entry}`, "service", "cnos", "account", entry], {
82
+ stdio: ["pipe", "ignore", "pipe"]
83
+ });
84
+ let stderr = "";
85
+ child.stdin?.end(value);
86
+ child.stderr?.on("data", (chunk) => {
87
+ stderr += chunk.toString();
88
+ });
89
+ child.on("error", reject);
90
+ child.on("close", (code) => {
91
+ if (code === 0) {
92
+ resolve();
93
+ return;
94
+ }
95
+ reject(new Error(stderr || `secret-tool exited with code ${code ?? 1}`));
96
+ });
97
+ });
98
+ }
99
+
100
+ // ../core/src/keychain/macos.ts
101
+ import { execFile as execFile2 } from "child_process";
102
+ import { promisify as promisify2 } from "util";
103
+ var execFileAsync2 = promisify2(execFile2);
104
+ async function readMacosKeychain(entry) {
105
+ try {
106
+ const { stdout } = await execFileAsync2("security", ["find-generic-password", "-a", "cnos", "-s", entry, "-w"]);
107
+ const value = stdout.trim();
108
+ return value.length > 0 ? value : void 0;
109
+ } catch {
110
+ return void 0;
111
+ }
112
+ }
113
+ async function writeMacosKeychain(entry, value) {
114
+ await execFileAsync2("security", ["add-generic-password", "-a", "cnos", "-s", entry, "-w", value, "-U"]);
115
+ }
116
+
117
+ // ../core/src/keychain/windows.ts
118
+ import { execFile as execFile3 } from "child_process";
119
+ import { promisify as promisify3 } from "util";
120
+ var execFileAsync3 = promisify3(execFile3);
121
+ function wrap(script) {
122
+ return ["-NoProfile", "-Command", script];
123
+ }
124
+ async function readWindowsKeychain(entry) {
125
+ try {
126
+ const { stdout } = await execFileAsync3(
127
+ "powershell",
128
+ wrap(`Add-Type -AssemblyName System.Runtime.WindowsRuntime; [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime] > $null; $vault = New-Object Windows.Security.Credentials.PasswordVault; $credential = $vault.Retrieve('cnos','${entry}'); $credential.RetrievePassword(); Write-Output $credential.Password`)
129
+ );
130
+ const value = stdout.trim();
131
+ return value.length > 0 ? value : void 0;
132
+ } catch {
133
+ return void 0;
134
+ }
135
+ }
136
+ async function writeWindowsKeychain(entry, value) {
137
+ await execFileAsync3(
138
+ "powershell",
139
+ wrap(`Add-Type -AssemblyName System.Runtime.WindowsRuntime; [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime] > $null; $vault = New-Object Windows.Security.Credentials.PasswordVault; try { $existing = $vault.Retrieve('cnos','${entry}'); $vault.Remove($existing) } catch {}; $credential = New-Object Windows.Security.Credentials.PasswordCredential('cnos','${entry}','${value}'); $vault.Add($credential)`)
140
+ );
141
+ }
142
+
143
+ // ../core/src/keychain/index.ts
144
+ async function readKeychain(entry) {
145
+ if (process.platform === "win32") {
146
+ return readWindowsKeychain(entry);
147
+ }
148
+ if (process.platform === "darwin") {
149
+ return readMacosKeychain(entry);
150
+ }
151
+ if (process.platform === "linux") {
152
+ return readLinuxKeychain(entry);
153
+ }
154
+ return void 0;
155
+ }
156
+ async function writeKeychain(entry, value) {
157
+ if (process.platform === "win32") {
158
+ await writeWindowsKeychain(entry, value);
159
+ return;
160
+ }
161
+ if (process.platform === "darwin") {
162
+ await writeMacosKeychain(entry, value);
163
+ return;
164
+ }
165
+ if (process.platform === "linux") {
166
+ await writeLinuxKeychain(entry, value);
167
+ return;
168
+ }
169
+ throw new CnosAuthenticationError(`OS keychain is not supported on platform "${process.platform}".`);
170
+ }
171
+
28
172
  // ../core/src/utils/path.ts
173
+ import { access } from "fs/promises";
174
+ import os from "os";
175
+ import path from "path";
29
176
  var PRIMARY_CNOS_DIR = ".cnos";
30
177
  var LEGACY_CNOS_DIR = "cnos";
31
178
  async function exists(filePath) {
@@ -120,10 +267,401 @@ function stringifyYaml(value) {
120
267
  return stringify(value);
121
268
  }
122
269
 
123
- // ../core/src/utils/secretStore.ts
124
- import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
125
- import { mkdir, readdir, readFile, writeFile } from "fs/promises";
270
+ // ../core/src/manifest/loadManifest.ts
271
+ import { readFile } from "fs/promises";
126
272
  import path2 from "path";
273
+
274
+ // ../core/src/manifest/normalizeManifest.ts
275
+ var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
276
+ var DEFAULT_LOADERS = [
277
+ "filesystem-values",
278
+ "filesystem-secrets",
279
+ "dotenv",
280
+ "process-env",
281
+ "cli-args"
282
+ ];
283
+ var DEFAULT_VALIDATORS = ["basic-schema"];
284
+ var DEFAULT_EXPORTERS = ["env", "public-env"];
285
+ var DEFAULT_INSPECTORS = ["provenance"];
286
+ var DEFAULT_FRAMEWORK_PREFIXES = {
287
+ next: "NEXT_PUBLIC_",
288
+ vite: "VITE_",
289
+ nuxt: "NUXT_PUBLIC_"
290
+ };
291
+ var DEFAULT_NAMESPACES = {
292
+ value: {
293
+ kind: "data",
294
+ shareable: true
295
+ },
296
+ secret: {
297
+ kind: "data",
298
+ shareable: false,
299
+ sensitive: true
300
+ },
301
+ meta: {
302
+ kind: "system",
303
+ shareable: false,
304
+ readonly: true
305
+ },
306
+ public: {
307
+ kind: "projection",
308
+ source: "promote",
309
+ shareable: true,
310
+ readonly: true
311
+ },
312
+ env: {
313
+ kind: "projection",
314
+ source: "envMapping",
315
+ shareable: true,
316
+ readonly: true
317
+ }
318
+ };
319
+ function validateResolveFrom(resolveFrom) {
320
+ const validValues = ["cli.profile", "env.CNOS_PROFILE", "default"];
321
+ for (const entry of resolveFrom) {
322
+ if (!validValues.includes(entry)) {
323
+ throw new CnosManifestError(`Unsupported profiles.resolveFrom entry: ${entry}`);
324
+ }
325
+ }
326
+ return resolveFrom;
327
+ }
328
+ function normalizeWorkspaceItems(items) {
329
+ return Object.fromEntries(
330
+ Object.entries(items ?? {}).map(([workspaceId, item]) => [
331
+ workspaceId,
332
+ {
333
+ extends: Array.isArray(item?.extends) ? item.extends.map((entry) => entry.trim()).filter(Boolean) : item?.extends ? [item.extends.trim()].filter(Boolean) : [],
334
+ ...item?.globalId?.trim() ? { globalId: item.globalId.trim() } : {}
335
+ }
336
+ ])
337
+ );
338
+ }
339
+ function normalizeNamespaces(namespaces) {
340
+ const normalized = Object.fromEntries(
341
+ Object.entries(namespaces ?? {}).map(([namespace, definition]) => [
342
+ namespace,
343
+ {
344
+ kind: definition.kind ?? "data",
345
+ shareable: definition.shareable ?? false,
346
+ ...definition.sensitive !== void 0 ? { sensitive: definition.sensitive } : {},
347
+ ...definition.readonly !== void 0 ? { readonly: definition.readonly } : {},
348
+ ...definition.source ? { source: definition.source } : {}
349
+ }
350
+ ])
351
+ );
352
+ return {
353
+ ...DEFAULT_NAMESPACES,
354
+ ...normalized
355
+ };
356
+ }
357
+ function normalizeVaults(vaults) {
358
+ return Object.fromEntries(
359
+ Object.entries(vaults ?? {}).map(([name, definition]) => {
360
+ const legacyPassphrase = definition.passphrase;
361
+ if (legacyPassphrase !== void 0) {
362
+ throw new CnosManifestError(
363
+ `Vault "${name}" uses legacy passphrase configuration. Use vaults.${name}.auth instead.`
364
+ );
365
+ }
366
+ const provider = definition.provider?.trim();
367
+ if (!provider) {
368
+ throw new CnosManifestError(`Vault "${name}" requires a provider`);
369
+ }
370
+ const normalizedAuth = normalizeVaultAuth(name, provider, definition.auth);
371
+ const normalizedMapping = Object.fromEntries(
372
+ Object.entries(definition.mapping ?? {}).filter(
373
+ (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
374
+ ).map(([envVar, logicalRef]) => [envVar.trim(), logicalRef.trim()]).filter(([envVar, logicalRef]) => envVar.length > 0 && logicalRef.length > 0)
375
+ );
376
+ return [
377
+ name,
378
+ {
379
+ provider,
380
+ auth: normalizedAuth,
381
+ ...Object.keys(normalizedMapping).length > 0 ? {
382
+ mapping: normalizedMapping
383
+ } : {}
384
+ }
385
+ ];
386
+ })
387
+ );
388
+ }
389
+ function normalizeAuthSources(value) {
390
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
391
+ return void 0;
392
+ }
393
+ const sources = Array.isArray(value.from) ? value.from : void 0;
394
+ const normalized = (sources ?? []).map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
395
+ return normalized.length > 0 ? normalized : void 0;
396
+ }
397
+ function normalizeVaultAuth(vaultName, provider, auth) {
398
+ if (provider === "local") {
399
+ const passphraseSources = normalizeAuthSources(auth?.passphrase);
400
+ const defaultToken = vaultName.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
401
+ const defaultSources = [
402
+ ...defaultToken ? [`env:CNOS_SECRET_PASSPHRASE_${defaultToken}`] : [],
403
+ "env:CNOS_SECRET_PASSPHRASE",
404
+ `keychain:cnos/${vaultName}`,
405
+ "prompt"
406
+ ];
407
+ return {
408
+ method: auth?.method ?? "passphrase",
409
+ passphrase: {
410
+ from: passphraseSources ?? defaultSources
411
+ },
412
+ ...auth?.config ? { config: auth.config } : {}
413
+ };
414
+ }
415
+ if (provider === "github-secrets") {
416
+ return {
417
+ method: auth?.method ?? "environment",
418
+ ...auth?.config ? { config: auth.config } : {}
419
+ };
420
+ }
421
+ return {
422
+ ...auth?.method ? { method: auth.method } : {},
423
+ ...normalizeAuthSources(auth?.passphrase) ? {
424
+ passphrase: {
425
+ from: normalizeAuthSources(auth?.passphrase) ?? []
426
+ }
427
+ } : {},
428
+ ...normalizeAuthSources(auth?.token) ? {
429
+ token: {
430
+ from: normalizeAuthSources(auth?.token) ?? []
431
+ }
432
+ } : {},
433
+ ...auth?.config ? { config: auth.config } : {}
434
+ };
435
+ }
436
+ function normalizeManifest(manifest) {
437
+ const version = manifest.version ?? 1;
438
+ if (version !== 1) {
439
+ throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
440
+ }
441
+ const projectName = manifest.project?.name?.trim();
442
+ if (!projectName) {
443
+ throw new CnosManifestError("Manifest requires project.name");
444
+ }
445
+ const defaultProfile = manifest.profiles?.default?.trim() || "base";
446
+ const workspaceItems = normalizeWorkspaceItems(manifest.workspaces?.items);
447
+ const resolveFrom = validateResolveFrom(manifest.profiles?.resolveFrom ?? DEFAULT_RESOLVE_FROM);
448
+ const filesystemValues = {
449
+ root: "./",
450
+ format: "yaml",
451
+ ...manifest.sources?.["filesystem-values"] ?? {}
452
+ };
453
+ const filesystemSecrets = {
454
+ root: "./",
455
+ format: "yaml",
456
+ ...manifest.sources?.["filesystem-secrets"] ?? {}
457
+ };
458
+ const dotenv = {
459
+ root: "./env",
460
+ ...manifest.sources?.dotenv ?? {}
461
+ };
462
+ return {
463
+ version: 1,
464
+ project: {
465
+ name: projectName
466
+ },
467
+ workspaces: {
468
+ ...manifest.workspaces?.default?.trim() ? {
469
+ default: manifest.workspaces.default.trim()
470
+ } : {},
471
+ global: {
472
+ enabled: manifest.workspaces?.global?.enabled ?? false,
473
+ ...manifest.workspaces?.global?.root?.trim() ? {
474
+ root: manifest.workspaces.global.root.trim()
475
+ } : {},
476
+ allowWrite: manifest.workspaces?.global?.allowWrite ?? false
477
+ },
478
+ items: workspaceItems
479
+ },
480
+ profiles: {
481
+ default: defaultProfile,
482
+ resolveFrom
483
+ },
484
+ plugins: {
485
+ loaders: manifest.plugins?.loaders ?? DEFAULT_LOADERS,
486
+ resolver: manifest.plugins?.resolver ?? "profile-aware",
487
+ validators: manifest.plugins?.validators ?? DEFAULT_VALIDATORS,
488
+ exporters: manifest.plugins?.exporters ?? DEFAULT_EXPORTERS,
489
+ inspectors: manifest.plugins?.inspectors ?? DEFAULT_INSPECTORS
490
+ },
491
+ sources: {
492
+ ...manifest.sources ?? {},
493
+ "filesystem-values": filesystemValues,
494
+ "filesystem-secrets": filesystemSecrets,
495
+ dotenv
496
+ },
497
+ resolution: {
498
+ precedence: manifest.resolution?.precedence ?? [
499
+ "filesystem-values",
500
+ "filesystem-secrets",
501
+ "dotenv",
502
+ "process-env",
503
+ "cli-args"
504
+ ],
505
+ arrayPolicy: manifest.resolution?.arrayPolicy ?? "replace"
506
+ },
507
+ envMapping: {
508
+ ...manifest.envMapping?.convention ? {
509
+ convention: manifest.envMapping.convention
510
+ } : {},
511
+ explicit: manifest.envMapping?.explicit ?? {}
512
+ },
513
+ public: {
514
+ promote: manifest.public?.promote ?? [],
515
+ frameworks: {
516
+ ...DEFAULT_FRAMEWORK_PREFIXES,
517
+ ...manifest.public?.frameworks ?? {}
518
+ }
519
+ },
520
+ namespaces: normalizeNamespaces(manifest.namespaces),
521
+ vaults: normalizeVaults(manifest.vaults),
522
+ writePolicy: {
523
+ define: {
524
+ defaultProfile: manifest.writePolicy?.define?.defaultProfile ?? defaultProfile,
525
+ targets: {
526
+ value: manifest.writePolicy?.define?.targets?.value ?? "./values/app.yml",
527
+ secret: manifest.writePolicy?.define?.targets?.secret ?? "./secrets/app.yml"
528
+ }
529
+ }
530
+ },
531
+ schema: manifest.schema ?? {}
532
+ };
533
+ }
534
+
535
+ // ../core/src/manifest/loadManifest.ts
536
+ async function loadManifest(options = {}) {
537
+ const manifestRoot = await resolveManifestRoot(options.root);
538
+ const manifestPath = path2.join(manifestRoot, "cnos.yml");
539
+ let source;
540
+ try {
541
+ source = await readFile(manifestPath, "utf8");
542
+ } catch {
543
+ throw new CnosManifestError("Unable to read CNOS manifest", manifestPath);
544
+ }
545
+ const rawManifest = parseYaml(source);
546
+ if (!rawManifest || typeof rawManifest !== "object") {
547
+ throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
548
+ }
549
+ return {
550
+ manifestRoot,
551
+ repoRoot: path2.dirname(manifestRoot),
552
+ manifestPath,
553
+ manifest: normalizeManifest(rawManifest),
554
+ rawManifest
555
+ };
556
+ }
557
+
558
+ // ../core/src/promotions/validatePromotion.ts
559
+ var DEFAULT_DATA_NAMESPACE = {
560
+ kind: "data",
561
+ shareable: false
562
+ };
563
+ function getNamespaceNameForKey(key) {
564
+ const [namespace] = key.split(".");
565
+ if (!namespace || !key.includes(".")) {
566
+ throw new CnosManifestError(`Logical key must be namespace-qualified: ${key}`);
567
+ }
568
+ return namespace;
569
+ }
570
+ function getNamespaceDefinition(manifest, namespaceOrKey) {
571
+ const namespace = namespaceOrKey.includes(".") ? getNamespaceNameForKey(namespaceOrKey) : namespaceOrKey;
572
+ return manifest.namespaces[namespace] ?? DEFAULT_DATA_NAMESPACE;
573
+ }
574
+ function ensureProjectionAllowed(manifest, key, target) {
575
+ const namespace = getNamespaceNameForKey(key);
576
+ const definition = getNamespaceDefinition(manifest, namespace);
577
+ if (definition.kind !== "data") {
578
+ throw new CnosManifestError(
579
+ `Cannot promote ${key} to ${target} because namespace "${namespace}" is not a data namespace.`
580
+ );
581
+ }
582
+ if (definition.sensitive) {
583
+ throw new CnosSecurityError(
584
+ `Cannot promote ${key} to ${target} because namespace "${namespace}" is sensitive.`
585
+ );
586
+ }
587
+ if (!definition.shareable) {
588
+ throw new CnosSecurityError(
589
+ `Cannot promote ${key} to ${target} because namespace "${namespace}" is not shareable.`
590
+ );
591
+ }
592
+ }
593
+ function validateProjectionIssue(manifest, key, target) {
594
+ try {
595
+ ensureProjectionAllowed(manifest, key, target);
596
+ return void 0;
597
+ } catch (error) {
598
+ return {
599
+ code: target === "public" ? "public.invalid-promotion" : "env.invalid-mapping",
600
+ key,
601
+ message: error instanceof Error ? error.message : String(error)
602
+ };
603
+ }
604
+ }
605
+
606
+ // ../core/src/secrets/sessionStore.ts
607
+ import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "fs/promises";
608
+ import path3 from "path";
609
+ function buildSessionRoot(processEnv = process.env) {
610
+ return path3.join(path3.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets")), "sessions");
611
+ }
612
+ function buildSessionPath(vault, processEnv) {
613
+ return path3.join(buildSessionRoot(processEnv), `${vault}.json`);
614
+ }
615
+ async function writeVaultSessionKey(vault, derivedKey, processEnv) {
616
+ const filePath = buildSessionPath(vault, processEnv);
617
+ await mkdir(path3.dirname(filePath), { recursive: true });
618
+ const document = {
619
+ version: 1,
620
+ vault,
621
+ derivedKey: derivedKey.toString("hex"),
622
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
623
+ };
624
+ await writeFile(filePath, JSON.stringify(document, null, 2), "utf8");
625
+ return filePath;
626
+ }
627
+ async function readVaultSessionKey(vault, processEnv) {
628
+ try {
629
+ const source = await readFile2(buildSessionPath(vault, processEnv), "utf8");
630
+ const document = JSON.parse(source);
631
+ if (document.version !== 1 || typeof document.derivedKey !== "string") {
632
+ return void 0;
633
+ }
634
+ const key = Buffer.from(document.derivedKey, "hex");
635
+ return key.length > 0 ? key : void 0;
636
+ } catch {
637
+ return void 0;
638
+ }
639
+ }
640
+ async function clearVaultSessionKey(vault, processEnv) {
641
+ await rm(buildSessionPath(vault, processEnv), { force: true });
642
+ }
643
+ async function clearAllVaultSessionKeys(processEnv) {
644
+ const root = buildSessionRoot(processEnv);
645
+ try {
646
+ const entries = await readdir(root);
647
+ await Promise.all(entries.map((entry) => rm(path3.join(root, entry), { force: true })));
648
+ } catch {
649
+ }
650
+ }
651
+
652
+ // ../core/src/utils/secretStore.ts
653
+ import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
654
+ import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2, stat, writeFile as writeFile2 } from "fs/promises";
655
+ import path4 from "path";
656
+ var KEY_LENGTH = 32;
657
+ var SALT_LENGTH = 32;
658
+ var IV_LENGTH = 12;
659
+ var AUTH_TAG_LENGTH = 16;
660
+ var PBKDF2_ITERATIONS = 6e5;
661
+ var KEYSTORE_VERSION = 1;
662
+ var METADATA_VERSION = 1;
663
+ var META_FILENAME = "meta.yml";
664
+ var KEYSTORE_FILENAME = "keystore.enc";
127
665
  function isObject(value) {
128
666
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
129
667
  }
@@ -131,103 +669,657 @@ function isSecretReference(value) {
131
669
  return isObject(value) && typeof value.provider === "string" && value.provider.trim().length > 0 && typeof value.ref === "string" && value.ref.trim().length > 0 && (value.vault === void 0 && true || typeof value.vault === "string" && value.vault.trim().length > 0) && Object.keys(value).every((key) => ["provider", "ref", "vault"].includes(key));
132
670
  }
133
671
  function resolveSecretStoreRoot(processEnv = process.env) {
134
- return path2.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
672
+ return path4.resolve(expandHomePath(processEnv.CNOS_SECRET_HOME ?? "~/.cnos/secrets"));
135
673
  }
136
- function resolveSecretVaultFile(storeRoot, vault = "default") {
137
- return path2.join(storeRoot, "vaults", `${vault}.json`);
674
+ function normalizeVaultToken(vault = "default") {
675
+ return vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
138
676
  }
139
- function resolveSecretStoreFile(storeRoot, ref, vault = "default") {
140
- return path2.join(storeRoot, "vaults", vault, "store", ...ref.split("/")).concat(".json");
677
+ function getVaultPassphraseEnvVar(vault = "default") {
678
+ const vaultToken = normalizeVaultToken(vault);
679
+ return vaultToken && vaultToken !== "DEFAULT" ? `CNOS_SECRET_PASSPHRASE_${vaultToken}` : "CNOS_SECRET_PASSPHRASE";
141
680
  }
142
- function deriveKey(passphrase, salt) {
143
- return scryptSync(passphrase, salt, 32);
681
+ function isPassphraseEnvRef(value) {
682
+ return typeof value === "string" && value.startsWith("env:") && value.length > 4;
683
+ }
684
+ function getVaultSessionKeyEnvVar(vault = "default") {
685
+ const vaultToken = normalizeVaultToken(vault);
686
+ return `__CNOS_VAULT_KEY_${vaultToken || "DEFAULT"}__`;
144
687
  }
145
688
  function resolveSecretPassphrase(vault = "default", processEnv = process.env) {
146
- const vaultToken = vault.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
147
- return processEnv[`CNOS_SECRET_PASSPHRASE_${vaultToken}`] ?? processEnv.CNOS_SECRET_PASSPHRASE;
689
+ return processEnv[getVaultPassphraseEnvVar(vault)] ?? processEnv.CNOS_SECRET_PASSPHRASE;
690
+ }
691
+ function resolveVaultSessionKey(vault = "default", processEnv = process.env) {
692
+ const encoded = processEnv[getVaultSessionKeyEnvVar(vault)];
693
+ if (!encoded) {
694
+ return readVaultSessionKey(vault, processEnv);
695
+ }
696
+ try {
697
+ const key = Buffer.from(encoded, "hex");
698
+ return key.length === KEY_LENGTH ? key : void 0;
699
+ } catch {
700
+ return void 0;
701
+ }
702
+ }
703
+ function deriveVaultKey(passphrase, salt, iterations = PBKDF2_ITERATIONS) {
704
+ return pbkdf2Sync(passphrase, salt, iterations, KEY_LENGTH, "sha512");
705
+ }
706
+ function buildMetaPath(storeRoot, vault = "default") {
707
+ return path4.join(storeRoot, "vaults", vault, META_FILENAME);
708
+ }
709
+ function resolveSecretVaultFile(storeRoot, vault = "default") {
710
+ return buildMetaPath(storeRoot, vault);
711
+ }
712
+ function buildKeystorePath(storeRoot, vault = "default") {
713
+ return path4.join(storeRoot, "vaults", vault, KEYSTORE_FILENAME);
714
+ }
715
+ function buildLegacyVaultFile(storeRoot, vault = "default") {
716
+ return path4.join(storeRoot, "vaults", `${vault}.json`);
717
+ }
718
+ function buildLegacyVaultStoreRoot(storeRoot, vault = "default") {
719
+ return path4.join(storeRoot, "vaults", vault, "store");
720
+ }
721
+ function assertVaultMetadata(value, filePath) {
722
+ if (!isObject(value)) {
723
+ throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
724
+ }
725
+ if (value.version !== METADATA_VERSION || value.algorithm !== "aes-256-gcm" || value.kdf !== "pbkdf2-sha512" || typeof value.iterations !== "number" || typeof value.salt !== "string" || typeof value.createdAt !== "string" || typeof value.secretCount !== "number") {
726
+ throw new CnosManifestError("Invalid CNOS vault metadata", filePath);
727
+ }
728
+ return value;
729
+ }
730
+ async function exists2(targetPath) {
731
+ try {
732
+ await stat(targetPath);
733
+ return true;
734
+ } catch {
735
+ return false;
736
+ }
737
+ }
738
+ async function detectLegacyVaultFormat(storeRoot, vault = "default") {
739
+ const legacyFile = buildLegacyVaultFile(storeRoot, vault);
740
+ const legacyStore = buildLegacyVaultStoreRoot(storeRoot, vault);
741
+ if (await exists2(legacyFile)) {
742
+ return legacyFile;
743
+ }
744
+ if (await exists2(legacyStore)) {
745
+ return legacyStore;
746
+ }
747
+ return void 0;
748
+ }
749
+ async function assertNoLegacyVaultFormat(storeRoot, vault = "default") {
750
+ const legacyPath = await detectLegacyVaultFormat(storeRoot, vault);
751
+ if (!legacyPath) {
752
+ return;
753
+ }
754
+ throw new CnosSecurityError(
755
+ `Legacy CNOS local vault format detected for vault "${vault}" at ${legacyPath}. CNOS 1.4 requires the new keystore format. Remove and recreate the vault.`
756
+ );
148
757
  }
149
- function encryptDocument(value, passphrase) {
150
- const salt = randomBytes(16);
151
- const iv = randomBytes(12);
152
- const key = deriveKey(passphrase, salt);
758
+ function encryptPayload(payload, key) {
759
+ const iv = randomBytes(IV_LENGTH);
153
760
  const cipher = createCipheriv("aes-256-gcm", key, iv);
154
- const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
761
+ const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
762
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
155
763
  const tag = cipher.getAuthTag();
764
+ return Buffer.concat([
765
+ Buffer.from(Uint32Array.of(KEYSTORE_VERSION).buffer),
766
+ iv,
767
+ tag,
768
+ ciphertext
769
+ ]);
770
+ }
771
+ function decryptPayload(buffer, key) {
772
+ if (buffer.length < 4 + IV_LENGTH + AUTH_TAG_LENGTH) {
773
+ throw new CnosSecurityError("Invalid CNOS local vault keystore");
774
+ }
775
+ const version = buffer.readUInt32LE(0);
776
+ if (version !== KEYSTORE_VERSION) {
777
+ throw new CnosSecurityError(`Unsupported CNOS local vault keystore version: ${version}`);
778
+ }
779
+ const ivOffset = 4;
780
+ const tagOffset = ivOffset + IV_LENGTH;
781
+ const cipherOffset = tagOffset + AUTH_TAG_LENGTH;
782
+ const iv = buffer.subarray(ivOffset, tagOffset);
783
+ const tag = buffer.subarray(tagOffset, cipherOffset);
784
+ const ciphertext = buffer.subarray(cipherOffset);
785
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
786
+ decipher.setAuthTag(tag);
787
+ try {
788
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
789
+ const payload = JSON.parse(plaintext);
790
+ if (!payload || !isObject(payload.secrets) || !isObject(payload.metadata)) {
791
+ throw new Error("invalid");
792
+ }
793
+ return {
794
+ secrets: Object.fromEntries(
795
+ Object.entries(payload.secrets).filter((entry) => typeof entry[1] === "string")
796
+ ),
797
+ metadata: Object.fromEntries(
798
+ Object.entries(payload.metadata).filter(
799
+ (entry) => isObject(entry[1]) && typeof entry[1].createdAt === "string" && typeof entry[1].updatedAt === "string"
800
+ )
801
+ )
802
+ };
803
+ } catch {
804
+ throw new CnosAuthenticationError("Failed to decrypt CNOS local vault. Check vault authentication.");
805
+ }
806
+ }
807
+ function buildInitialPayload() {
156
808
  return {
157
- version: 1,
809
+ secrets: {},
810
+ metadata: {}
811
+ };
812
+ }
813
+ async function writeVaultFiles(storeRoot, vault, meta, payload, key) {
814
+ const metaPath = buildMetaPath(storeRoot, vault);
815
+ const keystorePath = buildKeystorePath(storeRoot, vault);
816
+ await mkdir2(path4.dirname(metaPath), { recursive: true });
817
+ await writeFile2(metaPath, stringifyYaml(meta), "utf8");
818
+ await writeFile2(keystorePath, encryptPayload(payload, key));
819
+ }
820
+ async function readVaultMetadata(storeRoot, vault = "default") {
821
+ await assertNoLegacyVaultFormat(storeRoot, vault);
822
+ const metaPath = buildMetaPath(storeRoot, vault);
823
+ try {
824
+ const source = await readFile3(metaPath, "utf8");
825
+ return assertVaultMetadata(parseYaml(source), metaPath);
826
+ } catch (error) {
827
+ if (error.code === "ENOENT") {
828
+ return void 0;
829
+ }
830
+ throw error;
831
+ }
832
+ }
833
+ async function listSecretVaults(storeRoot) {
834
+ const vaultRoot = path4.join(storeRoot, "vaults");
835
+ try {
836
+ const entries = await readdir2(vaultRoot, { withFileTypes: true });
837
+ const vaults = await Promise.all(
838
+ entries.filter((entry) => entry.isDirectory()).map(async (entry) => await exists2(path4.join(vaultRoot, entry.name, META_FILENAME)) ? entry.name : void 0)
839
+ );
840
+ return vaults.filter((value) => Boolean(value)).sort((left, right) => left.localeCompare(right));
841
+ } catch {
842
+ return [];
843
+ }
844
+ }
845
+ async function createSecretVault(storeRoot, vault, passphrase) {
846
+ const normalizedVault = vault.trim() || "default";
847
+ await assertNoLegacyVaultFormat(storeRoot, normalizedVault);
848
+ const salt = randomBytes(SALT_LENGTH);
849
+ const key = deriveVaultKey(passphrase, salt, PBKDF2_ITERATIONS);
850
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
851
+ const meta = {
852
+ version: METADATA_VERSION,
158
853
  algorithm: "aes-256-gcm",
854
+ kdf: "pbkdf2-sha512",
855
+ iterations: PBKDF2_ITERATIONS,
159
856
  salt: salt.toString("base64"),
160
- iv: iv.toString("base64"),
161
- tag: tag.toString("base64"),
162
- ciphertext: ciphertext.toString("base64")
857
+ createdAt,
858
+ secretCount: 0
163
859
  };
860
+ await writeVaultFiles(storeRoot, normalizedVault, meta, buildInitialPayload(), key);
861
+ return buildMetaPath(storeRoot, normalizedVault);
164
862
  }
165
- function decryptDocument(document, passphrase) {
166
- const salt = Buffer.from(document.salt, "base64");
167
- const iv = Buffer.from(document.iv, "base64");
168
- const tag = Buffer.from(document.tag, "base64");
169
- const ciphertext = Buffer.from(document.ciphertext, "base64");
170
- const key = deriveKey(passphrase, salt);
171
- const decipher = createDecipheriv("aes-256-gcm", key, iv);
172
- decipher.setAuthTag(tag);
173
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
174
- return plaintext.toString("utf8");
863
+ async function ensureSecretVault(storeRoot, vault, passphrase) {
864
+ const normalizedVault = vault.trim() || "default";
865
+ const meta = await readVaultMetadata(storeRoot, normalizedVault);
866
+ if (meta) {
867
+ return buildMetaPath(storeRoot, normalizedVault);
868
+ }
869
+ return createSecretVault(storeRoot, normalizedVault, passphrase);
870
+ }
871
+ function resolveConfiguredVaultPassphrase(definition, vault = "default", processEnv = process.env) {
872
+ if (definition?.provider !== "local") {
873
+ return void 0;
874
+ }
875
+ const configuredSources = definition.auth?.passphrase?.from ?? [];
876
+ for (const source of configuredSources) {
877
+ if (source.startsWith("env:")) {
878
+ const value = processEnv[source.slice(4)];
879
+ if (value) {
880
+ return value;
881
+ }
882
+ }
883
+ }
884
+ return resolveSecretPassphrase(vault, processEnv);
885
+ }
886
+ async function resolveVaultAccessKey(storeRoot, definition, vault = "default", processEnv = process.env) {
887
+ if (definition?.provider !== "local") {
888
+ return definition?.provider === "github-secrets" ? {
889
+ method: definition.auth?.method ?? "environment",
890
+ ...definition?.auth?.config ? { config: definition.auth.config } : {}
891
+ } : void 0;
892
+ }
893
+ const sessionKey = await resolveVaultSessionKey(vault, processEnv);
894
+ if (sessionKey) {
895
+ return {
896
+ derivedKey: sessionKey,
897
+ method: "keychain",
898
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
899
+ };
900
+ }
901
+ const passphrase = resolveConfiguredVaultPassphrase(definition, vault, processEnv);
902
+ if (passphrase) {
903
+ return {
904
+ passphrase,
905
+ method: "passphrase",
906
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
907
+ };
908
+ }
909
+ const metadata = await readVaultMetadata(storeRoot, vault);
910
+ if (!metadata) {
911
+ return void 0;
912
+ }
913
+ throw new CnosAuthenticationError(
914
+ `Cannot authenticate to vault "${vault}". Set ${getVaultPassphraseEnvVar(vault)} or run cnos vault auth ${vault}.`
915
+ );
916
+ }
917
+ async function loadVaultPayload(storeRoot, vault, auth) {
918
+ const meta = await readVaultMetadata(storeRoot, vault);
919
+ if (!meta) {
920
+ throw new CnosManifestError(`Missing CNOS vault metadata for "${vault}"`);
921
+ }
922
+ const salt = Buffer.from(meta.salt, "base64");
923
+ const key = auth.derivedKey ?? (auth.passphrase ? deriveVaultKey(auth.passphrase, salt, meta.iterations) : void 0);
924
+ if (!key) {
925
+ throw new CnosAuthenticationError(`Vault "${vault}" requires authentication before access.`);
926
+ }
927
+ const buffer = await readFile3(buildKeystorePath(storeRoot, vault));
928
+ return {
929
+ meta,
930
+ payload: decryptPayload(buffer, key),
931
+ key
932
+ };
933
+ }
934
+ async function writeLocalSecret(storeRoot, ref, value, authOrPassphrase, vault = "default") {
935
+ const auth = typeof authOrPassphrase === "string" ? {
936
+ passphrase: authOrPassphrase,
937
+ method: "passphrase"
938
+ } : authOrPassphrase;
939
+ if (auth.passphrase) {
940
+ await ensureSecretVault(storeRoot, vault, auth.passphrase);
941
+ } else {
942
+ const meta2 = await readVaultMetadata(storeRoot, vault);
943
+ if (!meta2) {
944
+ throw new CnosAuthenticationError(`Vault "${vault}" requires passphrase-based authentication for initial creation.`);
945
+ }
946
+ }
947
+ const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
948
+ const now = (/* @__PURE__ */ new Date()).toISOString();
949
+ const existing = payload.metadata[ref];
950
+ payload.secrets[ref] = value;
951
+ payload.metadata[ref] = {
952
+ createdAt: existing?.createdAt ?? now,
953
+ updatedAt: now
954
+ };
955
+ const nextMeta = {
956
+ ...meta,
957
+ secretCount: Object.keys(payload.secrets).length
958
+ };
959
+ await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
960
+ return buildKeystorePath(storeRoot, vault);
961
+ }
962
+ async function deleteLocalSecret(storeRoot, ref, auth, vault = "default") {
963
+ const { meta, payload, key } = await loadVaultPayload(storeRoot, vault, auth);
964
+ if (!(ref in payload.secrets)) {
965
+ return false;
966
+ }
967
+ delete payload.secrets[ref];
968
+ delete payload.metadata[ref];
969
+ const nextMeta = {
970
+ ...meta,
971
+ secretCount: Object.keys(payload.secrets).length
972
+ };
973
+ await writeVaultFiles(storeRoot, vault, nextMeta, payload, key);
974
+ return true;
975
+ }
976
+ async function readLocalSecret(storeRoot, ref, auth, vault = "default") {
977
+ const { payload } = await loadVaultPayload(storeRoot, vault, auth);
978
+ const value = payload.secrets[ref];
979
+ if (value === void 0) {
980
+ throw new CnosManifestError(`Missing local secret ref "${ref}" in vault "${vault}"`);
981
+ }
982
+ return value;
983
+ }
984
+ async function listLocalSecrets(storeRoot, auth, vault = "default") {
985
+ const { payload } = await loadVaultPayload(storeRoot, vault, auth);
986
+ return Object.keys(payload.secrets).sort((left, right) => left.localeCompare(right));
987
+ }
988
+ function resolveVaultDefinition(vaults, vault = "default") {
989
+ const definition = vaults?.[vault];
990
+ const provider = definition?.provider ?? "local";
991
+ return {
992
+ name: vault,
993
+ provider,
994
+ ...definition?.auth ? { auth: definition.auth } : {},
995
+ ...definition?.mapping ? { mapping: definition.mapping } : {},
996
+ requiresAuthentication: provider === "local"
997
+ };
998
+ }
999
+ async function removeLocalVaultFiles(storeRoot, vault = "default") {
1000
+ await rm2(path4.join(storeRoot, "vaults", vault), { recursive: true, force: true });
1001
+ }
1002
+
1003
+ // ../core/src/secrets/providers/github.ts
1004
+ var GithubSecretsVaultProvider = class {
1005
+ constructor(vaultId, definition, processEnv = process.env) {
1006
+ this.vaultId = vaultId;
1007
+ this.definition = definition;
1008
+ this.processEnv = processEnv;
1009
+ }
1010
+ vaultId;
1011
+ definition;
1012
+ processEnv;
1013
+ authenticated = false;
1014
+ async authenticate(_authConfig) {
1015
+ void _authConfig;
1016
+ this.authenticated = true;
1017
+ }
1018
+ isAuthenticated() {
1019
+ return this.authenticated;
1020
+ }
1021
+ resolveEnvVar(ref) {
1022
+ if (this.processEnv[ref] !== void 0) {
1023
+ return ref;
1024
+ }
1025
+ return Object.entries(this.definition.mapping ?? {}).find(([, logicalRef]) => logicalRef === ref)?.[0];
1026
+ }
1027
+ async batchGet(refs) {
1028
+ this.authenticated = true;
1029
+ const resolved = /* @__PURE__ */ new Map();
1030
+ for (const ref of Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right))) {
1031
+ const envVar = this.resolveEnvVar(ref);
1032
+ const value = envVar ? this.processEnv[envVar] : void 0;
1033
+ if (value !== void 0) {
1034
+ resolved.set(ref, value);
1035
+ }
1036
+ }
1037
+ return resolved;
1038
+ }
1039
+ async get(ref) {
1040
+ const envVar = this.resolveEnvVar(ref);
1041
+ this.authenticated = true;
1042
+ return envVar ? this.processEnv[envVar] : void 0;
1043
+ }
1044
+ async set(ref, value) {
1045
+ void ref;
1046
+ void value;
1047
+ throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be written by CNOS.`);
1048
+ }
1049
+ async delete(ref) {
1050
+ void ref;
1051
+ throw new Error(`Vault "${this.vaultId}" is environment-backed and cannot be mutated by CNOS.`);
1052
+ }
1053
+ async list() {
1054
+ return Object.values(this.definition.mapping ?? {}).sort((left, right) => left.localeCompare(right));
1055
+ }
1056
+ };
1057
+
1058
+ // ../core/src/secrets/auditLog.ts
1059
+ import { appendFile, mkdir as mkdir3 } from "fs/promises";
1060
+ import path5 from "path";
1061
+ async function appendAuditEvent(event, processEnv = process.env) {
1062
+ const auditFile = processEnv.CNOS_AUDIT_FILE ?? path5.join(resolveSecretStoreRoot(processEnv), "audit", "access.log");
1063
+ await mkdir3(path5.dirname(auditFile), { recursive: true });
1064
+ await appendFile(
1065
+ auditFile,
1066
+ `${JSON.stringify({
1067
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1068
+ ...event
1069
+ })}
1070
+ `,
1071
+ "utf8"
1072
+ );
1073
+ }
1074
+
1075
+ // ../core/src/secrets/providers/local.ts
1076
+ var LocalSecretVaultProvider = class _LocalSecretVaultProvider {
1077
+ constructor(vaultId, definition, processEnv = process.env, storeRoot = resolveSecretStoreRoot(processEnv)) {
1078
+ this.vaultId = vaultId;
1079
+ this.processEnv = processEnv;
1080
+ this.storeRoot = storeRoot;
1081
+ this.definition = definition;
1082
+ }
1083
+ vaultId;
1084
+ processEnv;
1085
+ storeRoot;
1086
+ authConfig;
1087
+ definition;
1088
+ static fromVaults(vaults, vaultId, processEnv) {
1089
+ const definition = resolveVaultDefinition(vaults, vaultId);
1090
+ return new _LocalSecretVaultProvider(vaultId, definition, processEnv);
1091
+ }
1092
+ async authenticate(authConfig) {
1093
+ this.authConfig = authConfig;
1094
+ await this.list();
1095
+ }
1096
+ isAuthenticated() {
1097
+ return Boolean(this.authConfig);
1098
+ }
1099
+ async requireAuth() {
1100
+ if (this.authConfig) {
1101
+ return this.authConfig;
1102
+ }
1103
+ const resolved = await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
1104
+ if (!resolved) {
1105
+ throw new CnosAuthenticationError(
1106
+ `Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
1107
+ );
1108
+ }
1109
+ this.authConfig = resolved;
1110
+ return resolved;
1111
+ }
1112
+ async batchGet(refs) {
1113
+ const auth = await this.requireAuth();
1114
+ const entries = await Promise.all(
1115
+ Array.from(new Set(refs)).sort((left, right) => left.localeCompare(right)).map(async (ref) => [ref, await readLocalSecret(this.storeRoot, ref, auth, this.vaultId)])
1116
+ );
1117
+ return new Map(entries);
1118
+ }
1119
+ async get(ref) {
1120
+ const auth = await this.requireAuth();
1121
+ try {
1122
+ return await readLocalSecret(this.storeRoot, ref, auth, this.vaultId);
1123
+ } catch {
1124
+ return void 0;
1125
+ }
1126
+ }
1127
+ async set(ref, value) {
1128
+ const auth = await this.requireAuth();
1129
+ await writeLocalSecret(this.storeRoot, ref, value, auth, this.vaultId);
1130
+ await appendAuditEvent(
1131
+ {
1132
+ action: "write",
1133
+ vault: this.vaultId,
1134
+ ref,
1135
+ caller: "cli"
1136
+ },
1137
+ this.processEnv
1138
+ );
1139
+ }
1140
+ async delete(ref) {
1141
+ const auth = await this.requireAuth();
1142
+ await deleteLocalSecret(this.storeRoot, ref, auth, this.vaultId);
1143
+ await appendAuditEvent(
1144
+ {
1145
+ action: "delete",
1146
+ vault: this.vaultId,
1147
+ ref,
1148
+ caller: "cli"
1149
+ },
1150
+ this.processEnv
1151
+ );
1152
+ }
1153
+ async list() {
1154
+ const auth = this.authConfig ?? await resolveVaultAccessKey(this.storeRoot, this.definition, this.vaultId, this.processEnv);
1155
+ if (!auth) {
1156
+ throw new CnosAuthenticationError(
1157
+ `Cannot authenticate to vault "${this.vaultId}". Set the configured passphrase env var or run cnos vault auth ${this.vaultId}.`
1158
+ );
1159
+ }
1160
+ this.authConfig = auth;
1161
+ return listLocalSecrets(this.storeRoot, auth, this.vaultId);
1162
+ }
1163
+ };
1164
+
1165
+ // ../core/src/secrets/providers/registry.ts
1166
+ function createSecretVaultProvider(vaultId, definition, processEnv) {
1167
+ if (definition.provider === "local") {
1168
+ return new LocalSecretVaultProvider(vaultId, definition, processEnv);
1169
+ }
1170
+ if (definition.provider === "github-secrets") {
1171
+ return new GithubSecretsVaultProvider(vaultId, definition, processEnv);
1172
+ }
1173
+ throw new CnosManifestError(`Unsupported vault provider: ${definition.provider}`);
1174
+ }
1175
+
1176
+ // ../core/src/secrets/prompt.ts
1177
+ import readline from "readline";
1178
+ import { Writable } from "stream";
1179
+ async function promptHidden(message) {
1180
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1181
+ return void 0;
1182
+ }
1183
+ const mutableStdout = new WritableMask();
1184
+ const rl = readline.createInterface({
1185
+ input: process.stdin,
1186
+ output: mutableStdout,
1187
+ terminal: true
1188
+ });
1189
+ try {
1190
+ mutableStdout.muted = true;
1191
+ const value = await new Promise((resolve) => {
1192
+ rl.question(message, resolve);
1193
+ });
1194
+ process.stdout.write("\n");
1195
+ return value;
1196
+ } finally {
1197
+ rl.close();
1198
+ }
1199
+ }
1200
+ var WritableMask = class extends Writable {
1201
+ muted = false;
1202
+ _write(chunk, _encoding, callback) {
1203
+ if (!this.muted) {
1204
+ process.stdout.write(chunk);
1205
+ }
1206
+ callback();
1207
+ }
1208
+ };
1209
+
1210
+ // ../core/src/secrets/resolveAuth.ts
1211
+ function toAuthError(vaultId, sources) {
1212
+ return new CnosAuthenticationError(
1213
+ `Cannot authenticate to vault "${vaultId}". Tried: ${sources.join(", ")}. Set ${getVaultPassphraseEnvVar(vaultId)} or run cnos vault auth ${vaultId}.`
1214
+ );
1215
+ }
1216
+ async function resolveVaultAuth(vaultId, definition, processEnv = process.env) {
1217
+ const sessionKey = await resolveVaultSessionKey(vaultId, processEnv);
1218
+ if (sessionKey) {
1219
+ return {
1220
+ derivedKey: sessionKey,
1221
+ method: "keychain",
1222
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1223
+ };
1224
+ }
1225
+ if (definition.provider === "github-secrets") {
1226
+ return {
1227
+ method: definition.auth?.method ?? "environment",
1228
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1229
+ };
1230
+ }
1231
+ const sources = definition.auth?.passphrase?.from ?? [getVaultPassphraseEnvVar(vaultId)];
1232
+ for (const source of sources) {
1233
+ if (source.startsWith("env:")) {
1234
+ const value = processEnv[source.slice(4)];
1235
+ if (value) {
1236
+ return {
1237
+ passphrase: value,
1238
+ method: "passphrase",
1239
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1240
+ };
1241
+ }
1242
+ }
1243
+ if (source.startsWith("keychain:")) {
1244
+ const value = await readKeychain(source.slice("keychain:".length));
1245
+ if (value) {
1246
+ return {
1247
+ derivedKey: Buffer.from(value, "hex"),
1248
+ method: "keychain",
1249
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1250
+ };
1251
+ }
1252
+ }
1253
+ if (source === "prompt") {
1254
+ const value = await promptHidden(`Enter passphrase for vault "${vaultId}": `);
1255
+ if (value) {
1256
+ return {
1257
+ passphrase: value,
1258
+ method: "passphrase",
1259
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1260
+ };
1261
+ }
1262
+ }
1263
+ }
1264
+ const fallback = resolveSecretPassphrase(vaultId, processEnv);
1265
+ if (fallback) {
1266
+ return {
1267
+ passphrase: fallback,
1268
+ method: "passphrase",
1269
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
1270
+ };
1271
+ }
1272
+ throw toAuthError(vaultId, [getVaultSessionKeyEnvVar(vaultId), ...sources]);
175
1273
  }
176
- async function createSecretVault(storeRoot, vault, passphrase) {
177
- const normalizedVault = vault.trim() || "default";
178
- const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
179
- await mkdir(path2.dirname(filePath), { recursive: true });
180
- const document = {
181
- version: 1,
182
- name: normalizedVault,
183
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
184
- verifier: encryptDocument(`cnos-vault:${normalizedVault}`, passphrase)
185
- };
186
- await writeFile(filePath, JSON.stringify(document, null, 2), "utf8");
187
- return filePath;
1274
+
1275
+ // ../core/src/runtime/projection.ts
1276
+ function setNestedValue(target, pathSegments, value) {
1277
+ const [head, ...tail] = pathSegments;
1278
+ if (!head) {
1279
+ return;
1280
+ }
1281
+ if (tail.length === 0) {
1282
+ target[head] = value;
1283
+ return;
1284
+ }
1285
+ const current = target[head];
1286
+ const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
1287
+ target[head] = nextTarget;
1288
+ setNestedValue(nextTarget, tail, value);
188
1289
  }
189
- async function ensureSecretVault(storeRoot, vault, passphrase) {
190
- const normalizedVault = vault.trim() || "default";
191
- const filePath = resolveSecretVaultFile(storeRoot, normalizedVault);
192
- try {
193
- await readFile(filePath, "utf8");
194
- return filePath;
195
- } catch (error) {
196
- if (error.code !== "ENOENT") {
197
- throw error;
1290
+ function toNamespaceObject(graph, namespace) {
1291
+ const output = {};
1292
+ const resolvedEntries = Array.from(graph.entries.values()).sort(
1293
+ (left, right) => left.key.localeCompare(right.key)
1294
+ );
1295
+ for (const entry of resolvedEntries) {
1296
+ if (namespace && entry.namespace !== namespace) {
1297
+ continue;
198
1298
  }
1299
+ const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
1300
+ setNestedValue(output, valuePath.split("."), entry.value);
199
1301
  }
200
- return createSecretVault(storeRoot, normalizedVault, passphrase);
1302
+ return output;
201
1303
  }
202
- async function listSecretVaults(storeRoot) {
203
- const vaultRoot = path2.join(storeRoot, "vaults");
204
- try {
205
- const entries = await readdir(vaultRoot, { withFileTypes: true });
206
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.replace(/\.json$/, "")).sort((left, right) => left.localeCompare(right));
207
- } catch {
208
- return [];
209
- }
1304
+
1305
+ // ../core/src/runtime/read.ts
1306
+ function readValue(graph, key) {
1307
+ return graph.entries.get(key)?.value;
210
1308
  }
211
- async function writeLocalSecret(storeRoot, ref, value, passphrase, vault = "default") {
212
- await ensureSecretVault(storeRoot, vault, passphrase);
213
- const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
214
- await mkdir(path2.dirname(filePath), { recursive: true });
215
- await writeFile(filePath, JSON.stringify(encryptDocument(value, passphrase), null, 2), "utf8");
216
- return filePath;
1309
+
1310
+ // ../core/src/runtime/readOr.ts
1311
+ function readOrValue(graph, key, fallback) {
1312
+ const value = readValue(graph, key);
1313
+ return value === void 0 ? fallback : value;
217
1314
  }
218
- async function readLocalSecret(storeRoot, ref, passphrase, vault = "default") {
219
- if (!passphrase) {
220
- throw new CnosManifestError(
221
- `Missing CNOS secret passphrase for local secret ref "${ref}". Set CNOS_SECRET_PASSPHRASE or pass processEnv explicitly.`
222
- );
223
- }
224
- const filePath = resolveSecretStoreFile(storeRoot, ref, vault);
225
- const source = await readFile(filePath, "utf8");
226
- const document = JSON.parse(source);
227
- if (document.version !== 1 || document.algorithm !== "aes-256-gcm" || typeof document.salt !== "string" || typeof document.iv !== "string" || typeof document.tag !== "string" || typeof document.ciphertext !== "string") {
228
- throw new CnosManifestError("Invalid local secret document", filePath);
1315
+
1316
+ // ../core/src/runtime/require.ts
1317
+ function requireValue(graph, key) {
1318
+ const value = readValue(graph, key);
1319
+ if (value === void 0) {
1320
+ throw new CnosKeyNotFoundError(key);
229
1321
  }
230
- return decryptDocument(document, passphrase);
1322
+ return value;
231
1323
  }
232
1324
 
233
1325
  // ../core/src/runtime/toEnv.ts
@@ -254,6 +1346,10 @@ function toEnv(graph, manifest, options = {}) {
254
1346
  if (!entry) {
255
1347
  continue;
256
1348
  }
1349
+ const namespaceDefinition = getNamespaceDefinition(manifest, entry.namespace);
1350
+ if (namespaceDefinition.kind !== "data" || !namespaceDefinition.shareable || namespaceDefinition.sensitive) {
1351
+ continue;
1352
+ }
257
1353
  if (entry.namespace === "secret" && !includeSecrets) {
258
1354
  continue;
259
1355
  }
@@ -265,64 +1361,9 @@ function toEnv(graph, manifest, options = {}) {
265
1361
  return output;
266
1362
  }
267
1363
 
268
- // ../core/src/utils/envNaming.ts
269
- function normalizeMappingConfig(config = {}) {
270
- return {
271
- convention: config.convention,
272
- explicit: config.explicit ?? {}
273
- };
274
- }
275
- function toScreamingSnakeSegment(segment) {
276
- return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
277
- }
278
- function toScreamingSnake(path8) {
279
- return path8.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
280
- }
281
- function fromScreamingSnake(path8) {
282
- return path8.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
283
- }
284
- function logicalKeyToEnvVar(key, config = {}) {
285
- const normalized = normalizeMappingConfig(config);
286
- const explicitEntry = Object.entries(normalized.explicit).find(([, logicalKey]) => logicalKey === key);
287
- if (explicitEntry) {
288
- return explicitEntry[0];
289
- }
290
- if (normalized.convention !== "SCREAMING_SNAKE") {
291
- return void 0;
292
- }
293
- if (key.startsWith("value.")) {
294
- return toScreamingSnake(key.slice("value.".length));
295
- }
296
- if (key.startsWith("secret.")) {
297
- return `SECRET_${toScreamingSnake(key.slice("secret.".length))}`;
298
- }
299
- return void 0;
300
- }
301
- function envVarToLogicalKey(envVar, config = {}) {
302
- const normalized = normalizeMappingConfig(config);
303
- const explicitMatch = normalized.explicit[envVar];
304
- if (explicitMatch) {
305
- return explicitMatch;
306
- }
307
- if (normalized.convention !== "SCREAMING_SNAKE") {
308
- return void 0;
309
- }
310
- if (envVar.startsWith("SECRET_")) {
311
- const stripped = envVar.slice("SECRET_".length);
312
- if (!stripped) {
313
- return void 0;
314
- }
315
- return `secret.${fromScreamingSnake(stripped)}`;
316
- }
317
- if (!/^[A-Z][A-Z0-9_]*$/.test(envVar)) {
318
- return void 0;
319
- }
320
- return `value.${fromScreamingSnake(envVar)}`;
321
- }
322
-
323
1364
  // ../core/src/runtime/toPublicEnv.ts
324
- function fallbackValueEnvVar(key) {
325
- return key.replace(/^value\./, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
1365
+ function fallbackPublicEnvVar(valuePath) {
1366
+ return valuePath.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
326
1367
  }
327
1368
  function normalizeEnvValue2(value) {
328
1369
  if (value === void 0 || value === null) {
@@ -349,22 +1390,12 @@ function resolvePublicPrefix(manifest, options) {
349
1390
  }
350
1391
  return configuredPrefix;
351
1392
  }
352
- function ensurePublicPromotionKey(key) {
353
- if (!key.startsWith("value.")) {
354
- throw new CnosManifestError(`public.promote may only contain value.* keys: ${key}`);
355
- }
356
- }
357
1393
  function toPublicEnv(graph, manifest, options = {}) {
358
1394
  const prefix = resolvePublicPrefix(manifest, options);
359
1395
  const output = {};
360
- const promotions = [...manifest.public.promote].sort((left, right) => left.localeCompare(right));
361
- for (const key of promotions) {
362
- ensurePublicPromotionKey(key);
363
- const resolved = graph.entries.get(key);
364
- if (!resolved) {
365
- continue;
366
- }
367
- const baseEnvVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? fallbackValueEnvVar(key);
1396
+ const promotions = Array.from(graph.entries.values()).filter((entry) => entry.namespace === "public").sort((left, right) => left.key.localeCompare(right.key));
1397
+ for (const resolved of promotions) {
1398
+ const baseEnvVar = fallbackPublicEnvVar(stripNamespace(resolved.key));
368
1399
  const envVar = prefix && !baseEnvVar.startsWith(prefix) ? `${prefix}${baseEnvVar}` : baseEnvVar;
369
1400
  output[envVar] = normalizeEnvValue2(resolved.value);
370
1401
  }
@@ -372,55 +1403,23 @@ function toPublicEnv(graph, manifest, options = {}) {
372
1403
  }
373
1404
 
374
1405
  // ../core/src/runtime/dump.ts
375
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
376
- import path3 from "path";
377
-
378
- // ../core/src/runtime/projection.ts
379
- function setNestedValue(target, pathSegments, value) {
380
- const [head, ...tail] = pathSegments;
381
- if (!head) {
382
- return;
383
- }
384
- if (tail.length === 0) {
385
- target[head] = value;
386
- return;
387
- }
388
- const current = target[head];
389
- const nextTarget = current && typeof current === "object" && !Array.isArray(current) ? current : {};
390
- target[head] = nextTarget;
391
- setNestedValue(nextTarget, tail, value);
392
- }
393
- function toNamespaceObject(graph, namespace) {
394
- const output = {};
395
- const resolvedEntries = Array.from(graph.entries.values()).sort(
396
- (left, right) => left.key.localeCompare(right.key)
397
- );
398
- for (const entry of resolvedEntries) {
399
- if (namespace && entry.namespace !== namespace) {
400
- continue;
401
- }
402
- const valuePath = namespace ? stripNamespace(entry.key) : entry.key;
403
- setNestedValue(output, valuePath.split("."), entry.value);
404
- }
405
- return output;
406
- }
407
-
408
- // ../core/src/runtime/dump.ts
1406
+ import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
1407
+ import path6 from "path";
409
1408
  function buildDumpFiles(graph, options = {}) {
410
- const basePath = options.flatten ? "" : path3.posix.join("workspaces", graph.workspace.workspaceId);
1409
+ const basePath = options.flatten ? "" : path6.posix.join("workspaces", graph.workspace.workspaceId);
411
1410
  const values = toNamespaceObject(graph, "value");
412
1411
  const secrets = toNamespaceObject(graph, "secret");
413
1412
  const files = [];
414
1413
  if (Object.keys(values).length > 0) {
415
1414
  files.push({
416
- path: path3.posix.join(basePath, "values", graph.profile, "app.yml"),
1415
+ path: path6.posix.join(basePath, "values", graph.profile, "app.yml"),
417
1416
  namespace: "value",
418
1417
  content: stringifyYaml(values)
419
1418
  });
420
1419
  }
421
1420
  if (Object.keys(secrets).length > 0) {
422
1421
  files.push({
423
- path: path3.posix.join(basePath, "secrets", graph.profile, "app.yml"),
1422
+ path: path6.posix.join(basePath, "secrets", graph.profile, "app.yml"),
424
1423
  namespace: "secret",
425
1424
  content: stringifyYaml(secrets)
426
1425
  });
@@ -436,12 +1435,12 @@ function planDump(graph, options = {}) {
436
1435
  };
437
1436
  }
438
1437
  async function writeDump(graph, options) {
439
- const root = path3.resolve(options.to);
1438
+ const root = path6.resolve(options.to);
440
1439
  const plan = planDump(graph, options);
441
1440
  for (const file of plan.files) {
442
- const destination = path3.join(root, file.path);
443
- await mkdir2(path3.dirname(destination), { recursive: true });
444
- await writeFile2(destination, file.content, "utf8");
1441
+ const destination = path6.join(root, file.path);
1442
+ await mkdir4(path6.dirname(destination), { recursive: true });
1443
+ await writeFile3(destination, file.content, "utf8");
445
1444
  }
446
1445
  return {
447
1446
  ...plan,
@@ -462,6 +1461,61 @@ function flattenObject(value, prefix = "") {
462
1461
  }, {});
463
1462
  }
464
1463
 
1464
+ // ../core/src/utils/envNaming.ts
1465
+ function normalizeMappingConfig(config = {}) {
1466
+ return {
1467
+ convention: config.convention,
1468
+ explicit: config.explicit ?? {}
1469
+ };
1470
+ }
1471
+ function toScreamingSnakeSegment(segment) {
1472
+ return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
1473
+ }
1474
+ function toScreamingSnake(path10) {
1475
+ return path10.split(".").map((segment) => toScreamingSnakeSegment(segment)).filter(Boolean).join("_");
1476
+ }
1477
+ function fromScreamingSnake(path10) {
1478
+ return path10.split("_").map((segment) => segment.trim().toLowerCase()).filter(Boolean).join(".");
1479
+ }
1480
+ function logicalKeyToEnvVar(key, config = {}) {
1481
+ const normalized = normalizeMappingConfig(config);
1482
+ const explicitEntry = Object.entries(normalized.explicit).find(([, logicalKey]) => logicalKey === key);
1483
+ if (explicitEntry) {
1484
+ return explicitEntry[0];
1485
+ }
1486
+ if (normalized.convention !== "SCREAMING_SNAKE") {
1487
+ return void 0;
1488
+ }
1489
+ if (key.startsWith("value.")) {
1490
+ return toScreamingSnake(key.slice("value.".length));
1491
+ }
1492
+ if (key.startsWith("secret.")) {
1493
+ return `SECRET_${toScreamingSnake(key.slice("secret.".length))}`;
1494
+ }
1495
+ return void 0;
1496
+ }
1497
+ function envVarToLogicalKey(envVar, config = {}) {
1498
+ const normalized = normalizeMappingConfig(config);
1499
+ const explicitMatch = normalized.explicit[envVar];
1500
+ if (explicitMatch) {
1501
+ return explicitMatch;
1502
+ }
1503
+ if (normalized.convention !== "SCREAMING_SNAKE") {
1504
+ return void 0;
1505
+ }
1506
+ if (envVar.startsWith("SECRET_")) {
1507
+ const stripped = envVar.slice("SECRET_".length);
1508
+ if (!stripped) {
1509
+ return void 0;
1510
+ }
1511
+ return `secret.${fromScreamingSnake(stripped)}`;
1512
+ }
1513
+ if (!/^[A-Z][A-Z0-9_]*$/.test(envVar)) {
1514
+ return void 0;
1515
+ }
1516
+ return `value.${fromScreamingSnake(envVar)}`;
1517
+ }
1518
+
465
1519
  // ../core/src/validation/envMapping.ts
466
1520
  function fallbackLogicalKeyToEnvVar(key) {
467
1521
  return key.replace(/^(value|secret)\./, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
@@ -475,7 +1529,8 @@ function validateEnvMappingCollisions(manifest, graph) {
475
1529
  ]);
476
1530
  const collisions = /* @__PURE__ */ new Map();
477
1531
  for (const key of candidates) {
478
- if (key.startsWith("meta.")) {
1532
+ const definition = getNamespaceDefinition(manifest, key);
1533
+ if (definition.kind !== "data") {
479
1534
  continue;
480
1535
  }
481
1536
  const envVar = logicalKeyToEnvVar(key, manifest.envMapping) ?? (key.startsWith("value.") || key.startsWith("secret.") ? fallbackLogicalKeyToEnvVar(key) : void 0);
@@ -494,11 +1549,7 @@ function validateEnvMappingCollisions(manifest, graph) {
494
1549
 
495
1550
  // ../core/src/validation/publicSafety.ts
496
1551
  function validatePublicSafety(manifest) {
497
- return manifest.public.promote.filter((key) => !key.startsWith("value.")).map((key) => ({
498
- code: "public.invalid-promotion",
499
- key,
500
- message: `public.promote may only include value.* keys: ${key}`
501
- }));
1552
+ return manifest.public.promote.map((key) => validateProjectionIssue(manifest, key, "public")).filter((issue) => Boolean(issue));
502
1553
  }
503
1554
 
504
1555
  // ../core/src/validation/workspaceSafety.ts
@@ -542,58 +1593,25 @@ async function validateRuntime(runtime) {
542
1593
  issues: validatePublicSafety(runtime.manifest)
543
1594
  },
544
1595
  {
545
- pluginId: "env-mapping",
546
- valid: true,
547
- issues: validateEnvMappingCollisions(runtime.manifest, runtime.graph)
548
- },
549
- {
550
- pluginId: "workspace-safety",
551
- valid: true,
552
- issues: validateWorkspaceSafety(runtime.manifest, runtime.graph)
553
- }
554
- ].map((result) => ({
555
- ...result,
556
- valid: result.issues.length === 0
557
- }));
558
- const results = [...pluginResults, ...builtInResults];
559
- const issues = results.flatMap((result) => result.issues);
560
- return {
561
- valid: issues.length === 0,
562
- issues,
563
- results
564
- };
565
- }
566
-
567
- // ../core/src/runtime/inspect.ts
568
- function inspectValue(graph, key) {
569
- const entry = graph.entries.get(key);
570
- if (!entry) {
571
- throw new CnosKeyNotFoundError(key);
572
- }
573
- return {
574
- key: entry.key,
575
- value: entry.value,
576
- namespace: entry.namespace,
577
- profile: graph.profile,
578
- profileSource: graph.profileSource,
579
- workspace: {
580
- id: graph.workspace.workspaceId,
581
- source: graph.workspace.workspaceSource,
582
- chain: graph.workspace.workspaceChain
583
- },
584
- winner: {
585
- sourceId: entry.winner.sourceId,
586
- pluginId: entry.winner.pluginId,
587
- workspaceId: entry.winner.workspaceId,
588
- ...entry.winner.origin ? { origin: entry.winner.origin } : {}
1596
+ pluginId: "env-mapping",
1597
+ valid: true,
1598
+ issues: validateEnvMappingCollisions(runtime.manifest, runtime.graph)
589
1599
  },
590
- overridden: entry.overridden.map((override) => ({
591
- sourceId: override.sourceId,
592
- pluginId: override.pluginId,
593
- workspaceId: override.workspaceId,
594
- value: override.value,
595
- ...override.origin ? { origin: override.origin } : {}
596
- }))
1600
+ {
1601
+ pluginId: "workspace-safety",
1602
+ valid: true,
1603
+ issues: validateWorkspaceSafety(runtime.manifest, runtime.graph)
1604
+ }
1605
+ ].map((result) => ({
1606
+ ...result,
1607
+ valid: result.issues.length === 0
1608
+ }));
1609
+ const results = [...pluginResults, ...builtInResults];
1610
+ const issues = results.flatMap((result) => result.issues);
1611
+ return {
1612
+ valid: issues.length === 0,
1613
+ issues,
1614
+ results
597
1615
  };
598
1616
  }
599
1617
 
@@ -608,174 +1626,13 @@ function createProvenanceInspector() {
608
1626
  };
609
1627
  }
610
1628
 
611
- // ../core/src/manifest/loadManifest.ts
612
- import { readFile as readFile2 } from "fs/promises";
613
- import path4 from "path";
614
-
615
- // ../core/src/manifest/normalizeManifest.ts
616
- var DEFAULT_RESOLVE_FROM = ["cli.profile", "env.CNOS_PROFILE", "default"];
617
- var DEFAULT_LOADERS = [
618
- "filesystem-values",
619
- "filesystem-secrets",
620
- "dotenv",
621
- "process-env",
622
- "cli-args"
623
- ];
624
- var DEFAULT_VALIDATORS = ["basic-schema"];
625
- var DEFAULT_EXPORTERS = ["env", "public-env"];
626
- var DEFAULT_INSPECTORS = ["provenance"];
627
- var DEFAULT_FRAMEWORK_PREFIXES = {
628
- next: "NEXT_PUBLIC_",
629
- vite: "VITE_",
630
- nuxt: "NUXT_PUBLIC_"
631
- };
632
- function validateResolveFrom(resolveFrom) {
633
- const validValues = ["cli.profile", "env.CNOS_PROFILE", "default"];
634
- for (const entry of resolveFrom) {
635
- if (!validValues.includes(entry)) {
636
- throw new CnosManifestError(`Unsupported profiles.resolveFrom entry: ${entry}`);
637
- }
638
- }
639
- return resolveFrom;
640
- }
641
- function normalizeWorkspaceItems(items) {
642
- return Object.fromEntries(
643
- Object.entries(items ?? {}).map(([workspaceId, item]) => [
644
- workspaceId,
645
- {
646
- extends: Array.isArray(item?.extends) ? item.extends.map((entry) => entry.trim()).filter(Boolean) : item?.extends ? [item.extends.trim()].filter(Boolean) : [],
647
- ...item?.globalId?.trim() ? { globalId: item.globalId.trim() } : {}
648
- }
649
- ])
650
- );
651
- }
652
- function normalizeManifest(manifest) {
653
- const version = manifest.version ?? 1;
654
- if (version !== 1) {
655
- throw new CnosManifestError(`Unsupported CNOS manifest version: ${version}`);
656
- }
657
- const projectName = manifest.project?.name?.trim();
658
- if (!projectName) {
659
- throw new CnosManifestError("Manifest requires project.name");
660
- }
661
- const defaultProfile = manifest.profiles?.default?.trim() || "base";
662
- const workspaceItems = normalizeWorkspaceItems(manifest.workspaces?.items);
663
- const resolveFrom = validateResolveFrom(manifest.profiles?.resolveFrom ?? DEFAULT_RESOLVE_FROM);
664
- const filesystemValues = {
665
- root: "./",
666
- format: "yaml",
667
- ...manifest.sources?.["filesystem-values"] ?? {}
668
- };
669
- const filesystemSecrets = {
670
- root: "./",
671
- format: "yaml",
672
- ...manifest.sources?.["filesystem-secrets"] ?? {}
673
- };
674
- const dotenv = {
675
- root: "./env",
676
- ...manifest.sources?.dotenv ?? {}
677
- };
678
- return {
679
- version: 1,
680
- project: {
681
- name: projectName
682
- },
683
- workspaces: {
684
- ...manifest.workspaces?.default?.trim() ? {
685
- default: manifest.workspaces.default.trim()
686
- } : {},
687
- global: {
688
- enabled: manifest.workspaces?.global?.enabled ?? false,
689
- ...manifest.workspaces?.global?.root?.trim() ? {
690
- root: manifest.workspaces.global.root.trim()
691
- } : {},
692
- allowWrite: manifest.workspaces?.global?.allowWrite ?? false
693
- },
694
- items: workspaceItems
695
- },
696
- profiles: {
697
- default: defaultProfile,
698
- resolveFrom
699
- },
700
- plugins: {
701
- loaders: manifest.plugins?.loaders ?? DEFAULT_LOADERS,
702
- resolver: manifest.plugins?.resolver ?? "profile-aware",
703
- validators: manifest.plugins?.validators ?? DEFAULT_VALIDATORS,
704
- exporters: manifest.plugins?.exporters ?? DEFAULT_EXPORTERS,
705
- inspectors: manifest.plugins?.inspectors ?? DEFAULT_INSPECTORS
706
- },
707
- sources: {
708
- ...manifest.sources ?? {},
709
- "filesystem-values": filesystemValues,
710
- "filesystem-secrets": filesystemSecrets,
711
- dotenv
712
- },
713
- resolution: {
714
- precedence: manifest.resolution?.precedence ?? [
715
- "filesystem-values",
716
- "filesystem-secrets",
717
- "dotenv",
718
- "process-env",
719
- "cli-args"
720
- ],
721
- arrayPolicy: manifest.resolution?.arrayPolicy ?? "replace"
722
- },
723
- envMapping: {
724
- ...manifest.envMapping?.convention ? {
725
- convention: manifest.envMapping.convention
726
- } : {},
727
- explicit: manifest.envMapping?.explicit ?? {}
728
- },
729
- public: {
730
- promote: manifest.public?.promote ?? [],
731
- frameworks: {
732
- ...DEFAULT_FRAMEWORK_PREFIXES,
733
- ...manifest.public?.frameworks ?? {}
734
- }
735
- },
736
- writePolicy: {
737
- define: {
738
- defaultProfile: manifest.writePolicy?.define?.defaultProfile ?? defaultProfile,
739
- targets: {
740
- value: manifest.writePolicy?.define?.targets?.value ?? "./values/app.yml",
741
- secret: manifest.writePolicy?.define?.targets?.secret ?? "./secrets/app.yml"
742
- }
743
- }
744
- },
745
- schema: manifest.schema ?? {}
746
- };
747
- }
748
-
749
- // ../core/src/manifest/loadManifest.ts
750
- async function loadManifest(options = {}) {
751
- const manifestRoot = await resolveManifestRoot(options.root);
752
- const manifestPath = path4.join(manifestRoot, "cnos.yml");
753
- let source;
754
- try {
755
- source = await readFile2(manifestPath, "utf8");
756
- } catch {
757
- throw new CnosManifestError("Unable to read CNOS manifest", manifestPath);
758
- }
759
- const rawManifest = parseYaml(source);
760
- if (!rawManifest || typeof rawManifest !== "object") {
761
- throw new CnosManifestError("CNOS manifest must be a YAML object", manifestPath);
762
- }
763
- return {
764
- manifestRoot,
765
- repoRoot: path4.dirname(manifestRoot),
766
- manifestPath,
767
- manifest: normalizeManifest(rawManifest),
768
- rawManifest
769
- };
770
- }
771
-
772
1629
  // ../core/src/manifest/loadWorkspaceFile.ts
773
- import { readFile as readFile3 } from "fs/promises";
774
- import path5 from "path";
1630
+ import { readFile as readFile4 } from "fs/promises";
1631
+ import path7 from "path";
775
1632
  async function loadWorkspaceFile(repoRoot) {
776
- const workspaceFilePath = path5.join(repoRoot, ".cnos-workspace.yml");
1633
+ const workspaceFilePath = path7.join(repoRoot, ".cnos-workspace.yml");
777
1634
  try {
778
- const source = await readFile3(workspaceFilePath, "utf8");
1635
+ const source = await readFile4(workspaceFilePath, "utf8");
779
1636
  const parsed = parseYaml(source);
780
1637
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
781
1638
  throw new CnosManifestError(".cnos-workspace.yml must be a YAML object", workspaceFilePath);
@@ -798,8 +1655,8 @@ async function loadWorkspaceFile(repoRoot) {
798
1655
  }
799
1656
 
800
1657
  // ../core/src/profiles/expandProfileChain.ts
801
- import { access as access2, readFile as readFile4 } from "fs/promises";
802
- import path6 from "path";
1658
+ import { access as access2, readFile as readFile5 } from "fs/promises";
1659
+ import path8 from "path";
803
1660
  async function fileExists(targetPath) {
804
1661
  try {
805
1662
  await access2(targetPath);
@@ -836,11 +1693,11 @@ async function loadProfileDefinition(profileName, options) {
836
1693
  return normalizeProfileDefinition(profileName, void 0);
837
1694
  }
838
1695
  for (const workspaceRoot of [...workspaceRoots].reverse()) {
839
- const profilePath = path6.join(workspaceRoot.path, "profiles", `${profileName}.yml`);
1696
+ const profilePath = path8.join(workspaceRoot.path, "profiles", `${profileName}.yml`);
840
1697
  if (!await fileExists(profilePath)) {
841
1698
  continue;
842
1699
  }
843
- const document = await readFile4(profilePath, "utf8");
1700
+ const document = await readFile5(profilePath, "utf8");
844
1701
  const parsed = parseYaml(document);
845
1702
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
846
1703
  throw new CnosManifestError("Profile definition must be a YAML object", profilePath);
@@ -848,7 +1705,7 @@ async function loadProfileDefinition(profileName, options) {
848
1705
  const definition = normalizeProfileDefinition(
849
1706
  profileName,
850
1707
  parsed,
851
- options.manifestRoot ? toPortablePath(path6.relative(path6.dirname(options.manifestRoot), profilePath)) : toPortablePath(profilePath)
1708
+ options.manifestRoot ? toPortablePath(path8.relative(path8.dirname(options.manifestRoot), profilePath)) : toPortablePath(profilePath)
852
1709
  );
853
1710
  if (definition.name !== profileName) {
854
1711
  throw new CnosManifestError(
@@ -936,6 +1793,50 @@ async function expandProfileChain(activeProfile, options = {}) {
936
1793
  };
937
1794
  }
938
1795
 
1796
+ // ../core/src/promotions/promoteToPublic.ts
1797
+ function toPublicKey(key) {
1798
+ const namespace = getNamespaceNameForKey(key);
1799
+ return namespace === "value" ? `public.${stripNamespace(key)}` : `public.${key}`;
1800
+ }
1801
+ function toPromotedConfigEntry(entry, key, promotedFrom) {
1802
+ return {
1803
+ ...entry,
1804
+ key,
1805
+ namespace: "public",
1806
+ sourceId: "public-promote",
1807
+ pluginId: "core",
1808
+ metadata: {
1809
+ ...entry.metadata ?? {},
1810
+ promotedFrom
1811
+ }
1812
+ };
1813
+ }
1814
+ function toPromotedResolvedEntry(entry) {
1815
+ const key = toPublicKey(entry.key);
1816
+ return {
1817
+ key,
1818
+ value: entry.value,
1819
+ namespace: "public",
1820
+ winner: toPromotedConfigEntry(entry.winner, key, entry.key),
1821
+ overridden: entry.overridden.map((override) => toPromotedConfigEntry(override, key, entry.key))
1822
+ };
1823
+ }
1824
+ function promoteToPublic(graph, manifest) {
1825
+ const entries = new Map(graph.entries);
1826
+ for (const key of manifest.public.promote) {
1827
+ ensureProjectionAllowed(manifest, key, "public");
1828
+ const resolved = graph.entries.get(key);
1829
+ if (!resolved) {
1830
+ continue;
1831
+ }
1832
+ entries.set(toPublicKey(key), toPromotedResolvedEntry(resolved));
1833
+ }
1834
+ return {
1835
+ ...graph,
1836
+ entries
1837
+ };
1838
+ }
1839
+
939
1840
  // ../core/src/profiles/resolveActiveProfile.ts
940
1841
  function resolveActiveProfile(manifest, options = {}) {
941
1842
  for (const source of manifest.profiles.resolveFrom) {
@@ -1051,7 +1952,7 @@ function createProfileAwareResolver() {
1051
1952
 
1052
1953
  // ../core/src/workspaces/resolveWorkspaceContext.ts
1053
1954
  import { access as access3 } from "fs/promises";
1054
- import path7 from "path";
1955
+ import path9 from "path";
1055
1956
 
1056
1957
  // ../core/src/workspaces/expandWorkspaceChain.ts
1057
1958
  function expandWorkspaceChain(workspaceId, items) {
@@ -1088,7 +1989,7 @@ function expandWorkspaceChain(workspaceId, items) {
1088
1989
  }
1089
1990
 
1090
1991
  // ../core/src/workspaces/resolveWorkspaceContext.ts
1091
- async function exists2(targetPath) {
1992
+ async function exists3(targetPath) {
1092
1993
  try {
1093
1994
  await access3(targetPath);
1094
1995
  return true;
@@ -1097,14 +1998,14 @@ async function exists2(targetPath) {
1097
1998
  }
1098
1999
  }
1099
2000
  async function resolveLocalWorkspaceRoot(manifestRoot, workspaceId) {
1100
- const workspaceRoot = path7.join(manifestRoot, "workspaces", workspaceId);
1101
- if (await exists2(workspaceRoot)) {
2001
+ const workspaceRoot = path9.join(manifestRoot, "workspaces", workspaceId);
2002
+ if (await exists3(workspaceRoot)) {
1102
2003
  return workspaceRoot;
1103
2004
  }
1104
2005
  const legacyMarkers = ["values", "secrets", "env", "profiles"].map(
1105
- (segment) => path7.join(manifestRoot, segment)
2006
+ (segment) => path9.join(manifestRoot, segment)
1106
2007
  );
1107
- if ((await Promise.all(legacyMarkers.map((marker) => exists2(marker)))).some(Boolean)) {
2008
+ if ((await Promise.all(legacyMarkers.map((marker) => exists3(marker)))).some(Boolean)) {
1108
2009
  return manifestRoot;
1109
2010
  }
1110
2011
  return workspaceRoot;
@@ -1144,26 +2045,26 @@ function resolveGlobalRoot(manifest, workspaceFile, options) {
1144
2045
  }
1145
2046
  if (options.globalRoot) {
1146
2047
  return {
1147
- value: path7.resolve(expandHomePath(options.globalRoot)),
2048
+ value: path9.resolve(expandHomePath(options.globalRoot)),
1148
2049
  source: "cli"
1149
2050
  };
1150
2051
  }
1151
2052
  if (workspaceFile?.globalRoot) {
1152
2053
  return {
1153
- value: path7.resolve(expandHomePath(workspaceFile.globalRoot)),
2054
+ value: path9.resolve(expandHomePath(workspaceFile.globalRoot)),
1154
2055
  source: "workspace-file"
1155
2056
  };
1156
2057
  }
1157
2058
  if (manifest.workspaces.global.root) {
1158
2059
  return {
1159
- value: path7.resolve(expandHomePath(manifest.workspaces.global.root)),
2060
+ value: path9.resolve(expandHomePath(manifest.workspaces.global.root)),
1160
2061
  source: "manifest"
1161
2062
  };
1162
2063
  }
1163
2064
  const cnosHome = options.processEnv?.CNOS_HOME;
1164
2065
  if (cnosHome) {
1165
2066
  return {
1166
- value: path7.resolve(expandHomePath(cnosHome)),
2067
+ value: path9.resolve(expandHomePath(cnosHome)),
1167
2068
  source: "CNOS_HOME"
1168
2069
  };
1169
2070
  }
@@ -1180,7 +2081,7 @@ async function resolveWorkspaceContext(manifest, options) {
1180
2081
  workspaceRoots.push({
1181
2082
  scope: "global",
1182
2083
  workspaceId: chainWorkspaceId,
1183
- path: path7.join(globalRoot.value, "workspaces", globalWorkspaceId)
2084
+ path: path9.join(globalRoot.value, "workspaces", globalWorkspaceId)
1184
2085
  });
1185
2086
  }
1186
2087
  }
@@ -1370,49 +2271,120 @@ async function runPipeline(options) {
1370
2271
  return collectedEntries.flat();
1371
2272
  }
1372
2273
 
1373
- // ../core/src/runtime/read.ts
1374
- function readValue(graph, key) {
1375
- return graph.entries.get(key)?.value;
1376
- }
2274
+ // ../core/src/secrets/secretCache.ts
2275
+ var SecretCache = class {
2276
+ cache = /* @__PURE__ */ new Map();
2277
+ authenticated = /* @__PURE__ */ new Set();
2278
+ load(vaultId, secrets) {
2279
+ this.authenticated.add(vaultId);
2280
+ for (const [ref, value] of secrets) {
2281
+ this.cache.set(`${vaultId}:${ref}`, value);
2282
+ }
2283
+ }
2284
+ isVaultAuthenticated(vaultId) {
2285
+ return this.authenticated.has(vaultId);
2286
+ }
2287
+ get(vaultId, ref) {
2288
+ return this.cache.get(`${vaultId}:${ref}`);
2289
+ }
2290
+ clear(vaultId) {
2291
+ if (!vaultId) {
2292
+ this.cache.clear();
2293
+ this.authenticated.clear();
2294
+ return;
2295
+ }
2296
+ this.authenticated.delete(vaultId);
2297
+ for (const key of Array.from(this.cache.keys())) {
2298
+ if (key.startsWith(`${vaultId}:`)) {
2299
+ this.cache.delete(key);
2300
+ }
2301
+ }
2302
+ }
2303
+ };
1377
2304
 
1378
- // ../core/src/runtime/readOr.ts
1379
- function readOrValue(graph, key, fallback) {
1380
- const value = readValue(graph, key);
1381
- return value === void 0 ? fallback : value;
2305
+ // ../core/src/secrets/batchResolve.ts
2306
+ function collectSecretDescriptors(graph) {
2307
+ return Array.from(graph.entries.values()).filter((entry) => entry.namespace === "secret" && isSecretReference(entry.value)).map((entry) => ({
2308
+ logicalKey: entry.key,
2309
+ ref: entry.value
2310
+ }));
1382
2311
  }
1383
-
1384
- // ../core/src/runtime/require.ts
1385
- function requireValue(graph, key) {
1386
- const value = readValue(graph, key);
1387
- if (value === void 0) {
1388
- throw new CnosKeyNotFoundError(key);
2312
+ async function batchResolveSecrets(graph, manifest, processEnv = process.env) {
2313
+ const cache = new SecretCache();
2314
+ const descriptors = collectSecretDescriptors(graph);
2315
+ const grouped = descriptors.reduce((accumulator, descriptor) => {
2316
+ const vaultId = descriptor.ref.vault ?? "default";
2317
+ const bucket = accumulator.get(vaultId) ?? [];
2318
+ bucket.push(descriptor);
2319
+ accumulator.set(vaultId, bucket);
2320
+ return accumulator;
2321
+ }, /* @__PURE__ */ new Map());
2322
+ for (const [vaultId, refs] of grouped) {
2323
+ const definition = manifest.vaults[vaultId] ?? { provider: "local", auth: { passphrase: { from: [] } } };
2324
+ const provider = createSecretVaultProvider(vaultId, definition, processEnv);
2325
+ const auth = await resolveVaultAuth(vaultId, definition, processEnv);
2326
+ await provider.authenticate(auth);
2327
+ const resolved = await provider.batchGet(refs.map((entry) => entry.ref.ref));
2328
+ cache.load(vaultId, resolved);
2329
+ await appendAuditEvent(
2330
+ {
2331
+ action: "batch_read",
2332
+ vault: vaultId,
2333
+ refs: Array.from(resolved.keys()).sort((left, right) => left.localeCompare(right)),
2334
+ caller: "runtime",
2335
+ workspace: graph.workspace.workspaceId,
2336
+ profile: graph.profile
2337
+ },
2338
+ processEnv
2339
+ );
1389
2340
  }
1390
- return value;
2341
+ return cache;
2342
+ }
2343
+ function resolveSecretEntryValue(key, value, cache) {
2344
+ if (!key.startsWith("secret.") || !isSecretReference(value)) {
2345
+ return value;
2346
+ }
2347
+ const vaultId = value.vault ?? "default";
2348
+ return cache.get(vaultId, value.ref) ?? value;
1391
2349
  }
1392
2350
 
1393
2351
  // ../core/src/orchestrator/runtime.ts
1394
- function createRuntime(manifest, graph, plugins = []) {
2352
+ function createRuntime(manifest, graph, plugins = [], secretCache) {
2353
+ function readLogicalKey(key) {
2354
+ const entry = graph.entries.get(key);
2355
+ if (!entry) {
2356
+ return void 0;
2357
+ }
2358
+ if (!secretCache) {
2359
+ return entry.value;
2360
+ }
2361
+ return resolveSecretEntryValue(key, entry.value, secretCache);
2362
+ }
1395
2363
  return {
1396
2364
  manifest,
1397
2365
  plugins,
1398
2366
  graph,
1399
2367
  read(key) {
1400
- return readValue(graph, key);
2368
+ return readLogicalKey(key);
1401
2369
  },
1402
2370
  require(key) {
1403
- return requireValue(graph, key);
2371
+ const value = readLogicalKey(key);
2372
+ if (value === void 0) {
2373
+ return requireValue(graph, key);
2374
+ }
2375
+ return value;
1404
2376
  },
1405
2377
  readOr(key, fallback) {
1406
2378
  return readOrValue(graph, key, fallback);
1407
2379
  },
1408
- value(path8) {
1409
- return readValue(graph, toLogicalKey("value", path8));
2380
+ value(path10) {
2381
+ return readLogicalKey(toLogicalKey("value", path10));
1410
2382
  },
1411
- secret(path8) {
1412
- return readValue(graph, toLogicalKey("secret", path8));
2383
+ secret(path10) {
2384
+ return readLogicalKey(toLogicalKey("secret", path10));
1413
2385
  },
1414
- meta(path8) {
1415
- return readValue(graph, toLogicalKey("meta", path8));
2386
+ meta(path10) {
2387
+ return readLogicalKey(toLogicalKey("meta", path10));
1416
2388
  },
1417
2389
  inspect(key) {
1418
2390
  return inspectValue(graph, key);
@@ -1527,6 +2499,9 @@ function appendMetaEntries(graph, cnosVersion) {
1527
2499
  }
1528
2500
  async function createCnos(options = {}) {
1529
2501
  const loadedManifest = await loadManifest(options.root ? { root: options.root } : {});
2502
+ for (const key of loadedManifest.manifest.public.promote) {
2503
+ ensureProjectionAllowed(loadedManifest.manifest, key, "public");
2504
+ }
1530
2505
  const workspaceFile = await loadWorkspaceFile(loadedManifest.repoRoot);
1531
2506
  const workspace = await resolveWorkspaceContext(loadedManifest.manifest, {
1532
2507
  manifestRoot: loadedManifest.manifestRoot,
@@ -1566,40 +2541,73 @@ async function createCnos(options = {}) {
1566
2541
  workspace
1567
2542
  });
1568
2543
  const schemaApplied = applySchemaRules(graph, loadedManifest.manifest.schema);
2544
+ const promotedGraph = promoteToPublic(schemaApplied.graph, loadedManifest.manifest);
2545
+ const secretCache = options.secretResolution === "lazy" ? void 0 : await batchResolveSecrets(promotedGraph, loadedManifest.manifest, options.processEnv);
1569
2546
  return createRuntime(
1570
2547
  loadedManifest.manifest,
1571
2548
  appendMetaEntries({
1572
- ...schemaApplied.graph,
2549
+ ...promotedGraph,
1573
2550
  profileSource: activeProfile.source
1574
2551
  }, options.cnosVersion),
1575
- plugins
2552
+ plugins,
2553
+ secretCache
1576
2554
  );
1577
2555
  }
1578
2556
 
1579
2557
  export {
1580
2558
  CnosManifestError,
2559
+ CnosSecurityError,
2560
+ CnosAuthenticationError,
2561
+ inspectValue,
1581
2562
  createProvenanceInspector,
2563
+ readKeychain,
2564
+ writeKeychain,
2565
+ resolveManifestRoot,
1582
2566
  resolveWorkspaceScopedPath,
1583
2567
  resolveConfigDocumentPath,
1584
2568
  toPortablePath,
1585
2569
  joinConfigPath,
2570
+ toLogicalKey,
1586
2571
  parseYaml,
1587
2572
  stringifyYaml,
2573
+ loadManifest,
2574
+ ensureProjectionAllowed,
1588
2575
  applySchemaRules,
2576
+ writeVaultSessionKey,
2577
+ clearVaultSessionKey,
2578
+ clearAllVaultSessionKeys,
1589
2579
  isSecretReference,
1590
2580
  resolveSecretStoreRoot,
1591
- resolveSecretVaultFile,
2581
+ getVaultPassphraseEnvVar,
2582
+ isPassphraseEnvRef,
2583
+ getVaultSessionKeyEnvVar,
1592
2584
  resolveSecretPassphrase,
1593
- createSecretVault,
2585
+ deriveVaultKey,
2586
+ resolveSecretVaultFile,
2587
+ detectLegacyVaultFormat,
2588
+ readVaultMetadata,
1594
2589
  listSecretVaults,
2590
+ createSecretVault,
2591
+ resolveConfiguredVaultPassphrase,
2592
+ resolveVaultAccessKey,
1595
2593
  writeLocalSecret,
2594
+ deleteLocalSecret,
1596
2595
  readLocalSecret,
2596
+ listLocalSecrets,
2597
+ resolveVaultDefinition,
2598
+ removeLocalVaultFiles,
2599
+ createSecretVaultProvider,
2600
+ resolveVaultAuth,
2601
+ toNamespaceObject,
2602
+ readValue,
2603
+ readOrValue,
2604
+ requireValue,
1597
2605
  toEnv,
1598
- envVarToLogicalKey,
1599
2606
  toPublicEnv,
1600
2607
  createCnos,
1601
2608
  planDump,
1602
2609
  writeDump,
2610
+ envVarToLogicalKey,
1603
2611
  flattenObject,
1604
2612
  validateRuntime
1605
2613
  };