@runeya/runeya 2.0.145 → 2.0.147
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/index.js +36 -25
- package/agent/index.js.map +1 -1
- package/agent-manager-46C4NIQ2.js +13 -0
- package/chat-CMNDMXIP.js +28 -0
- package/{chunk-ONVAH65J.js → chunk-2NE52CL5.js} +422 -2172
- package/chunk-2NE52CL5.js.map +1 -0
- package/chunk-4D3GCBPO.js +462 -0
- package/chunk-4D3GCBPO.js.map +1 -0
- package/{chunk-NCO4OOYT.js → chunk-4LJQIQMN.js} +2 -2
- package/{chunk-QF56Z6LI.js → chunk-5XG3X2TM.js} +29 -1481
- package/chunk-5XG3X2TM.js.map +1 -0
- package/chunk-6IBPK6UE.js +1063 -0
- package/chunk-6IBPK6UE.js.map +1 -0
- package/chunk-7QXP4FXD.js +969 -0
- package/chunk-7QXP4FXD.js.map +1 -0
- package/chunk-DA2DZBTL.js +43 -0
- package/chunk-DA2DZBTL.js.map +1 -0
- package/chunk-DFPZ4MTI.js +1041 -0
- package/chunk-DFPZ4MTI.js.map +1 -0
- package/{chunk-Q4RPY3VW.js → chunk-JNK6H5KH.js} +185 -1014
- package/chunk-JNK6H5KH.js.map +1 -0
- package/{chunk-FE53XX4M.js → chunk-NPMSBYXC.js} +3 -3
- package/{chunk-KS7A5CTW.js → chunk-VPSQJOSN.js} +12 -2
- package/{chunk-KS7A5CTW.js.map → chunk-VPSQJOSN.js.map} +1 -1
- package/chunk-WDHLEO5S.js +809 -0
- package/chunk-WDHLEO5S.js.map +1 -0
- package/{companion-HBYSY2D5.js → companion-XKMNRIJB.js} +10 -5
- package/environment-resolver-ZDTF3MHI.js +18 -0
- package/external-NMKQPROK.js +387 -0
- package/external-NMKQPROK.js.map +1 -0
- package/index.js +15 -6
- package/index.js.map +1 -1
- package/{install-gate-JAEK4BDZ.js → install-gate-OJJEUU4E.js} +2 -2
- package/mcp-ready-TAPBAOZY.js +9 -0
- package/mcp-server/external.d.ts +44 -0
- package/mcp-server/external.js +2679 -0
- package/mcp-server/external.js.map +1 -0
- package/mcp-server/index.js +159 -39
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/launch.d.ts +25 -1
- package/mcp-server/launch.js +22 -1
- package/mcp-server/launch.js.map +1 -1
- package/package.json +1 -1
- package/project-store-FLXTAD3J.js +23 -0
- package/project-store-FLXTAD3J.js.map +1 -0
- package/settings-manager-XPLBO35P.js +10 -0
- package/settings-manager-XPLBO35P.js.map +1 -0
- package/{src-IODFUZ7A.js → src-KWO3CKZU.js} +1083 -551
- package/src-KWO3CKZU.js.map +1 -0
- package/{vault-resolver-PN3TYCLJ.js → vault-resolver-E7KPKA5C.js} +2 -2
- package/vault-resolver-E7KPKA5C.js.map +1 -0
- package/web/assets/{AgentScalarChatInterface.vue-npQgDxeP.js → AgentScalarChatInterface.vue-DKBinB_l.js} +2 -2
- package/web/assets/AgentTerminalPage-CixU7x6O.js +1 -0
- package/web/assets/AiChatModals-BvGUd-QD.css +1 -0
- package/web/assets/AiChatModals-C-hXImkO.js +4 -0
- package/web/assets/AppLayout-At9wk0sx.js +1 -0
- package/web/assets/{AppLayout-tsw5Ow0w.css → AppLayout-BlhwflMq.css} +1 -1
- package/web/assets/AuditPage-BkqbHxjY.js +1 -0
- package/web/assets/CloudAccountPage-D5C2SCS9.js +1 -0
- package/web/assets/CloudMessagingPage-Bi57GGHO.js +2 -0
- package/web/assets/DashboardPage-dEQop9ql.js +1 -0
- package/web/assets/DirectoryPickerDialog-DIMSeDhw.js +1 -0
- package/web/assets/{EditorIcon-CypGLV6w.js → EditorIcon-W3Szca4J.js} +1 -1
- package/web/assets/EnvironmentForm-C5emavNh.js +1 -0
- package/web/assets/EnvironmentFormPage-BIoy8fsW.js +1 -0
- package/web/assets/EnvironmentListPage-BDYeh7XF.js +1 -0
- package/web/assets/{FlagChip-R_T5qYpL.js → FlagChip-DPtIBCjQ.js} +1 -1
- package/web/assets/KanbanBoardPage-BlCcjBwV.js +4 -0
- package/web/assets/KanbanListPage-DbXONxfC.js +1 -0
- package/web/assets/LaunchProjectPage-Cg92hHwQ.js +1 -0
- package/web/assets/LaunchServicesPage-CO8gOB9D.js +1 -0
- package/web/assets/LaunchServicesPage-h5o3zLcy.css +1 -0
- package/web/assets/{LaunchShell-CGNYr-do.js → LaunchShell-CDb21ihZ.js} +1 -1
- package/web/assets/LogViewer-DsusKk2b.js +3 -0
- package/web/assets/OrgKeyPrompt-CX9K7r9I.js +1 -0
- package/web/assets/ProcessControls-B7hWVqiR.css +1 -0
- package/web/assets/ProcessControls-TEZutuxq.js +1 -0
- package/web/assets/ProcessDetailPage-Cj23FECq.js +1 -0
- package/web/assets/ProcessMetrics-1P6iE2Il.js +1 -0
- package/web/assets/ProjectCreateDialog-D3aMWKfw.js +1 -0
- package/web/assets/{ProjectLocationGate-bUd_tFpI.js → ProjectLocationGate-DU0Jj8Nw.js} +1 -1
- package/web/assets/ProjectPublishDialog-D7sNKei3.js +1 -0
- package/web/assets/{RandomJobTestPage-BFNC827R.js → RandomJobTestPage-DQUMRHyf.js} +1 -1
- package/web/assets/{ScenarioEditorPage-vMoRt0Qn.css → ScenarioEditorPage-BSjSX4bw.css} +1 -1
- package/web/assets/ScenarioEditorPage-DvCdcY9m.js +14 -0
- package/web/assets/SendToChatDialog-BdWKjYmv.js +1 -0
- package/web/assets/ServiceControlsBar-B_J5m-Kc.js +1 -0
- package/web/assets/ServiceDetailPage-DhJcjFfY.js +2 -0
- package/web/assets/{ServiceDetailPage-COdaCZ6r.css → ServiceDetailPage-DnSf5cm9.css} +1 -1
- package/web/assets/ServiceFormPage-fp8NCHSv.js +1 -0
- package/web/assets/SettingsPage-NH2fQap4.css +1 -0
- package/web/assets/SettingsPage-s28BodWe.js +2086 -0
- package/web/assets/UnlockPage-BXXIPHW_.js +1 -0
- package/web/assets/VaultMigrationPage-BfNuKtkJ.js +1 -0
- package/web/assets/{VersionBadge-DBquJl76.js → VersionBadge-CW7czacg.js} +1 -1
- package/web/assets/{WikiPage-GtwpJPqf.js → WikiPage-BiwpCM3E.js} +1 -1
- package/web/assets/WikiView-CMch_06Z.js +1 -0
- package/web/assets/{add-scalar-classes-DhSSqZ5l.js → add-scalar-classes-Ckanjq1h.js} +14 -14
- package/web/assets/{agent.store-Co44dMZf.js → agent.store-piokQ9Dt.js} +1 -1
- package/web/assets/{auth-Bk1GP5sB.js → auth-BT_jf-5e.js} +2 -2
- package/web/assets/cloud-chat.store-CDKh4tIy.js +1 -0
- package/web/assets/{cloud-client-0zD_NI6q.js → cloud-client-CzPdZLDq.js} +2 -2
- package/web/assets/{cloud-session-Cjw54Y--.js → cloud-session-BYfn6YM4.js} +2 -2
- package/web/assets/floating-ui.dom-DF3oyfHd.js +1 -0
- package/web/assets/i18n-D0FT7rCj.js +1 -0
- package/web/assets/index-3LTzgeU-.css +1 -0
- package/web/assets/index-Ci2CNnit.js +309 -0
- package/web/assets/modal-iRaP1izE.js +5 -0
- package/web/assets/notification.store-CvvuTBVz.js +1 -0
- package/web/assets/{project-launch.store-B5nhdGdh.js → project-launch.store-CTmbl8UY.js} +1 -1
- package/web/assets/{project.store-Bm2JWNbd.js → project.store-CNC6_4r4.js} +1 -1
- package/web/assets/runtime-dom.esm-bundler-CyEVRK6B.js +1 -0
- package/web/assets/scenario.store-BIFQPXgl.js +7 -0
- package/web/assets/{service.store-XS50iGKo.js → service.store-B40U4QzI.js} +1 -1
- package/web/assets/settings-scope-CyWMCjRY.js +1 -0
- package/web/assets/settings.store-Dw6bAoWE.js +1 -0
- package/web/assets/{src-Cgix2VOi.js → src-CrNi4Pjg.js} +3 -3
- package/web/assets/src-DIBHat7I.js +3 -0
- package/web/assets/src-DtCW_XCh.js +1 -0
- package/web/assets/{src-BDZKZr9n.css → src-g1DRo8xZ.css} +1 -1
- package/web/assets/{trpc-CfuFkKgp.js → trpc-BAZGRZBD.js} +3 -3
- package/web/assets/{useCloudOrg-J8DAnocE.js → useCloudOrg-EnXFBwt1.js} +1 -1
- package/web/assets/useEnvVarAutocomplete-BxJh2Lw0.js +1 -0
- package/web/assets/useProjectCatalog-BrvUGv9A.js +1 -0
- package/web/assets/{useProjectCloudOrg-BnlKts7x.js → useProjectCloudOrg-Czxlg00k.js} +1 -1
- package/web/assets/{useServiceUrlHost-hKbKtPMu.js → useServiceUrlHost-Szz9y3rh.js} +1 -1
- package/web/assets/vue-i18n-o7yraQpR.js +3 -0
- package/web/index.html +21 -21
- package/agent-manager-THV3LJOF.js +0 -11
- package/chat-XYCHMTFE.js +0 -23
- package/chunk-ONVAH65J.js.map +0 -1
- package/chunk-Q4RPY3VW.js.map +0 -1
- package/chunk-QF56Z6LI.js.map +0 -1
- package/settings-manager-QVQRJO6V.js +0 -10
- package/src-IODFUZ7A.js.map +0 -1
- package/web/assets/AgentTerminalPage-BTH3yCS2.js +0 -1
- package/web/assets/AiChatModals-EfRuvId7.css +0 -1
- package/web/assets/AiChatModals-It9jnB26.js +0 -4
- package/web/assets/AppLayout-BTg2CADo.js +0 -1
- package/web/assets/AuditPage-2PKKGLZs.js +0 -1
- package/web/assets/CloudAccountPage-CHL61gLe.js +0 -1
- package/web/assets/CloudMessagingPage-cgKnABPH.js +0 -2
- package/web/assets/DashboardPage-CRntTMKY.js +0 -1
- package/web/assets/DirectoryPickerDialog-DCyhT3-W.js +0 -1
- package/web/assets/EnvironmentForm-C96PFWRE.js +0 -1
- package/web/assets/EnvironmentFormPage-ChorAd63.js +0 -1
- package/web/assets/EnvironmentListPage-Dw9ziaI8.js +0 -1
- package/web/assets/KanbanBoardPage-BZHbEj-q.js +0 -4
- package/web/assets/KanbanListPage-B0jlfKqs.js +0 -1
- package/web/assets/LaunchProjectPage-BVs1JwbV.js +0 -1
- package/web/assets/LaunchServicesPage-DoLDPqQq.css +0 -1
- package/web/assets/LaunchServicesPage-PBYJly9N.js +0 -1
- package/web/assets/LogViewer-BDObx29F.js +0 -3
- package/web/assets/OrgKeyPrompt-BbcwiwGw.js +0 -1
- package/web/assets/ProcessControls-BlpnVylq.css +0 -1
- package/web/assets/ProcessControls-DY0HTKde.js +0 -1
- package/web/assets/ProcessDetailPage-BZ9kIQqn.js +0 -1
- package/web/assets/ProcessMetrics-Crlfkelj.js +0 -1
- package/web/assets/ProjectCreateDialog-C1wVDPcK.js +0 -1
- package/web/assets/ProjectPublishDialog-APUS1WEN.js +0 -1
- package/web/assets/ScenarioEditorPage-DFVsMVu5.js +0 -14
- package/web/assets/SendToChatDialog-QHIkxNio.js +0 -1
- package/web/assets/ServiceControlsBar-_UQXb6g0.js +0 -1
- package/web/assets/ServiceDetailPage-DGIXgHwR.js +0 -2
- package/web/assets/ServiceFormPage-RliAfS3_.js +0 -1
- package/web/assets/SettingsPage-BSSHxxjM.js +0 -2086
- package/web/assets/SettingsPage-W8YszhUL.css +0 -1
- package/web/assets/UnlockPage-17EgfpJs.js +0 -1
- package/web/assets/VaultMigrationPage-DeMxpXXJ.js +0 -1
- package/web/assets/WikiView-BqaqxMxW.js +0 -1
- package/web/assets/cloud-chat.store-CadB14dZ.js +0 -1
- package/web/assets/editor.store-DmM6ZlTm.js +0 -1
- package/web/assets/floating-ui.dom-DWhsZyqT.js +0 -1
- package/web/assets/i18n-8FjeJaKW.js +0 -1
- package/web/assets/index-B9DJf2V0.js +0 -309
- package/web/assets/index-teBqdnuv.css +0 -1
- package/web/assets/modal-UK8xRSnh.js +0 -5
- package/web/assets/scenario.store-88w6P6s2.js +0 -7
- package/web/assets/settings-scope-CKPq86HZ.js +0 -1
- package/web/assets/settings.store-BCJ0LJQA.js +0 -1
- package/web/assets/src--GO-VSJ0.js +0 -5
- package/web/assets/src-DBipSD56.js +0 -1
- package/web/assets/useEnvVarAutocomplete-C6zOXYqi.js +0 -1
- package/web/assets/useProjectCatalog-BB5IR6o8.js +0 -1
- package/web/assets/vue-draggable-plus-BHsveB8w.css +0 -1
- package/web/assets/vue-draggable-plus-D5ni4jMk.js +0 -1
- /package/{agent-manager-THV3LJOF.js.map → agent-manager-46C4NIQ2.js.map} +0 -0
- /package/{chat-XYCHMTFE.js.map → chat-CMNDMXIP.js.map} +0 -0
- /package/{chunk-NCO4OOYT.js.map → chunk-4LJQIQMN.js.map} +0 -0
- /package/{chunk-FE53XX4M.js.map → chunk-NPMSBYXC.js.map} +0 -0
- /package/{companion-HBYSY2D5.js.map → companion-XKMNRIJB.js.map} +0 -0
- /package/{settings-manager-QVQRJO6V.js.map → environment-resolver-ZDTF3MHI.js.map} +0 -0
- /package/{install-gate-JAEK4BDZ.js.map → install-gate-OJJEUU4E.js.map} +0 -0
- /package/{vault-resolver-PN3TYCLJ.js.map → mcp-ready-TAPBAOZY.js.map} +0 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StoreDiskCoordinator,
|
|
3
|
+
decryptVariableSlots,
|
|
4
|
+
encryptVariableSlots,
|
|
5
|
+
ensureProjectsLoaded,
|
|
6
|
+
environmentStore,
|
|
7
|
+
extractEncryptedVariableSlots,
|
|
8
|
+
getEncryptionKey
|
|
9
|
+
} from "./chunk-DFPZ4MTI.js";
|
|
10
|
+
import {
|
|
11
|
+
fingerprintFiles,
|
|
12
|
+
renameWithRetry
|
|
13
|
+
} from "./chunk-4LJQIQMN.js";
|
|
14
|
+
import {
|
|
15
|
+
allVaultRoots,
|
|
16
|
+
keyedFilePath,
|
|
17
|
+
legacyMachineFilePath,
|
|
18
|
+
vaultIdOfRoot
|
|
19
|
+
} from "./chunk-VPSQJOSN.js";
|
|
20
|
+
import {
|
|
21
|
+
MASKED_VALUE,
|
|
22
|
+
isSharedWithAi
|
|
23
|
+
} from "./chunk-MZSDTA4A.js";
|
|
24
|
+
|
|
25
|
+
// ../server/src/services/environment-overrides-store.ts
|
|
26
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
27
|
+
import { dirname, join } from "path";
|
|
28
|
+
var FILENAME = "environment-overrides.json";
|
|
29
|
+
var EnvironmentOverridesStore = class {
|
|
30
|
+
data = {};
|
|
31
|
+
loaded = false;
|
|
32
|
+
/**
|
|
33
|
+
* Le coordinateur partagé par les stores : un seul rechargement à la fois,
|
|
34
|
+
* jamais pendant une écriture, et une substitution atomique à la fin.
|
|
35
|
+
*
|
|
36
|
+
* Ce store faisait sa propre cuisine et se marquait « chargé » avant ses
|
|
37
|
+
* `await` : toute lecture concurrente repartait avec une table vide — une
|
|
38
|
+
* surcharge déjà écrite paraissait absente, et l'assistant réclamait au
|
|
39
|
+
* déverrouillage des variables que ce poste avait déjà renseignées.
|
|
40
|
+
*/
|
|
41
|
+
disk = new StoreDiskCoordinator();
|
|
42
|
+
/** Ce que le disque portait au dernier chargement (cf. `load`). */
|
|
43
|
+
fingerprint = "";
|
|
44
|
+
diskEncryptedSlots = {};
|
|
45
|
+
/**
|
|
46
|
+
* Les surcharges dont le secret ne s'est pas ouvert.
|
|
47
|
+
*
|
|
48
|
+
* Vides en mémoire, elles doivent repartir chiffrées : sans cela, la
|
|
49
|
+
* sauvegarde suivante remplacerait par du blanc une valeur dont il ne
|
|
50
|
+
* manquait que la clé.
|
|
51
|
+
*/
|
|
52
|
+
unreadableSecrets = /* @__PURE__ */ new Set();
|
|
53
|
+
/** Which vault each override was read from, until its project is known. */
|
|
54
|
+
roots = /* @__PURE__ */ new Map();
|
|
55
|
+
/**
|
|
56
|
+
* An override belongs to the project whose variable it answers.
|
|
57
|
+
*
|
|
58
|
+
* Nothing in an override says so — they are keyed by variable id alone — so
|
|
59
|
+
* the owning project is resolved through the environment that defines the
|
|
60
|
+
* variable. Overrides are never synced: they stay in the project's vault on
|
|
61
|
+
* this machine, and no other machine sees them.
|
|
62
|
+
*
|
|
63
|
+
* An override whose variable no longer exists (a deleted environment, a row
|
|
64
|
+
* left behind) has no project to belong to. It stays wherever it was read,
|
|
65
|
+
* falling back to the key's own directory — never dropped, since the variable
|
|
66
|
+
* may simply not be loaded yet.
|
|
67
|
+
*/
|
|
68
|
+
async rootFor(variableId) {
|
|
69
|
+
const root = await environmentStore.rootOfVariable(variableId);
|
|
70
|
+
if (root) return root;
|
|
71
|
+
return this.roots.get(variableId) ?? dirname(await keyedFilePath(FILENAME));
|
|
72
|
+
}
|
|
73
|
+
/** Where earlier versions kept them: read once, rewritten to their vaults. */
|
|
74
|
+
getLegacyFilePath() {
|
|
75
|
+
return legacyMachineFilePath(FILENAME);
|
|
76
|
+
}
|
|
77
|
+
/** Les fichiers qui portent des surcharges : les coffres, et l'emplacement d'avant. */
|
|
78
|
+
async sourcePaths() {
|
|
79
|
+
const roots = await allVaultRoots();
|
|
80
|
+
return [
|
|
81
|
+
this.getLegacyFilePath(),
|
|
82
|
+
await keyedFilePath(FILENAME),
|
|
83
|
+
...roots.map((root) => join(root, FILENAME))
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Charger, et recharger quand les fichiers ont bougé sous nos pieds.
|
|
88
|
+
*
|
|
89
|
+
* L'empreinte se prend **après** `ensureProjectsLoaded` : la liste des coffres
|
|
90
|
+
* en dépend, et une empreinte prise sur une liste incomplète ne ressemblerait
|
|
91
|
+
* à aucune des suivantes — le store se rechargerait à chaque lecture.
|
|
92
|
+
*/
|
|
93
|
+
async load() {
|
|
94
|
+
await this.disk.load(async () => {
|
|
95
|
+
if (!getEncryptionKey()) return null;
|
|
96
|
+
await ensureProjectsLoaded();
|
|
97
|
+
const fingerprint = await fingerprintFiles(await this.sourcePaths());
|
|
98
|
+
if (this.loaded && fingerprint === this.fingerprint) return null;
|
|
99
|
+
const next = await this.readVaults();
|
|
100
|
+
return () => {
|
|
101
|
+
this.data = next.data;
|
|
102
|
+
this.diskEncryptedSlots = next.diskEncryptedSlots;
|
|
103
|
+
this.roots = next.roots;
|
|
104
|
+
this.unreadableSecrets = next.unreadableSecrets;
|
|
105
|
+
this.fingerprint = fingerprint;
|
|
106
|
+
this.loaded = true;
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Read every vault, then the places older versions used, into fresh tables
|
|
112
|
+
* that `load` swaps in at once.
|
|
113
|
+
*
|
|
114
|
+
* Variable ids are unique, so merging the files cannot collide; a vault read
|
|
115
|
+
* later simply wins over a legacy copy of the same override.
|
|
116
|
+
*/
|
|
117
|
+
async readVaults() {
|
|
118
|
+
const data = {};
|
|
119
|
+
const diskEncryptedSlots = {};
|
|
120
|
+
const roots = /* @__PURE__ */ new Map();
|
|
121
|
+
const unreadableSecrets = /* @__PURE__ */ new Set();
|
|
122
|
+
for (const path of await this.sourcePaths()) {
|
|
123
|
+
let parsed;
|
|
124
|
+
try {
|
|
125
|
+
parsed = JSON.parse(await readFile(path, "utf-8"));
|
|
126
|
+
} catch {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
Object.assign(
|
|
130
|
+
diskEncryptedSlots,
|
|
131
|
+
extractEncryptedVariableSlots(parsed)
|
|
132
|
+
);
|
|
133
|
+
const decrypted = decryptVariableSlots(
|
|
134
|
+
parsed,
|
|
135
|
+
"environment-overrides",
|
|
136
|
+
(variableId) => unreadableSecrets.add(variableId),
|
|
137
|
+
vaultIdOfRoot(dirname(path))
|
|
138
|
+
);
|
|
139
|
+
for (const [id, override] of Object.entries(decrypted)) {
|
|
140
|
+
data[id] = { value: override.value };
|
|
141
|
+
roots.set(id, dirname(path));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { data, diskEncryptedSlots, roots, unreadableSecrets };
|
|
145
|
+
}
|
|
146
|
+
save() {
|
|
147
|
+
return this.disk.save(() => this.doSave());
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Write each vault that holds — or held — an override.
|
|
151
|
+
*
|
|
152
|
+
* A vault emptied by this save is still written, as an empty object: an
|
|
153
|
+
* override that moved to its project's vault must not survive in the one it
|
|
154
|
+
* came from.
|
|
155
|
+
*/
|
|
156
|
+
async doSave() {
|
|
157
|
+
const byRoot = /* @__PURE__ */ new Map();
|
|
158
|
+
for (const root of this.roots.values()) byRoot.set(root, {});
|
|
159
|
+
for (const [variableId, override] of Object.entries(this.data)) {
|
|
160
|
+
const root = await this.rootFor(variableId);
|
|
161
|
+
this.roots.set(variableId, root);
|
|
162
|
+
const bucket = byRoot.get(root) ?? {};
|
|
163
|
+
bucket[variableId] = override;
|
|
164
|
+
byRoot.set(root, bucket);
|
|
165
|
+
}
|
|
166
|
+
for (const [root, overrides] of byRoot) {
|
|
167
|
+
const encrypted = this.keepUnreadableSecrets(encryptVariableSlots(
|
|
168
|
+
overrides,
|
|
169
|
+
this.diskEncryptedSlots,
|
|
170
|
+
vaultIdOfRoot(root)
|
|
171
|
+
));
|
|
172
|
+
Object.assign(
|
|
173
|
+
this.diskEncryptedSlots,
|
|
174
|
+
extractEncryptedVariableSlots(encrypted)
|
|
175
|
+
);
|
|
176
|
+
await mkdir(root, { recursive: true });
|
|
177
|
+
const path = join(root, FILENAME);
|
|
178
|
+
const tmpPath = path + ".tmp";
|
|
179
|
+
await writeFile(tmpPath, JSON.stringify(encrypted, null, 2), "utf-8");
|
|
180
|
+
await renameWithRetry(tmpPath, path);
|
|
181
|
+
await chmod(path, 384);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Rendre au disque le chiffré d'une surcharge illisible, plutôt qu'un blanc.
|
|
186
|
+
*
|
|
187
|
+
* Pendant du garde-fou du store des environnements : tant que la valeur en
|
|
188
|
+
* mémoire est vide, c'est que personne n'a retouché la variable et que son
|
|
189
|
+
* chiffré fait toujours foi.
|
|
190
|
+
*/
|
|
191
|
+
keepUnreadableSecrets(overrides) {
|
|
192
|
+
if (this.unreadableSecrets.size === 0) return overrides;
|
|
193
|
+
const result = { ...overrides };
|
|
194
|
+
for (const variableId of this.unreadableSecrets) {
|
|
195
|
+
const ciphertext = this.diskEncryptedSlots[variableId];
|
|
196
|
+
const override = result[variableId];
|
|
197
|
+
if (!ciphertext || !override || override.value.value) continue;
|
|
198
|
+
result[variableId] = { ...override, value: { ...override.value, value: ciphertext } };
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
async getOverride(variableId) {
|
|
203
|
+
await this.load();
|
|
204
|
+
return this.data[variableId] ?? null;
|
|
205
|
+
}
|
|
206
|
+
async setOverride(variableId, override) {
|
|
207
|
+
await this.load();
|
|
208
|
+
this.data[variableId] = { value: override.value };
|
|
209
|
+
await this.save();
|
|
210
|
+
}
|
|
211
|
+
async removeOverride(variableId) {
|
|
212
|
+
await this.load();
|
|
213
|
+
if (!this.data[variableId]) return;
|
|
214
|
+
delete this.data[variableId];
|
|
215
|
+
this.unreadableSecrets.delete(variableId);
|
|
216
|
+
await this.save();
|
|
217
|
+
}
|
|
218
|
+
async getAll() {
|
|
219
|
+
await this.load();
|
|
220
|
+
return { ...this.data };
|
|
221
|
+
}
|
|
222
|
+
async removeOverridesForVariables(variableIds) {
|
|
223
|
+
await this.load();
|
|
224
|
+
let changed = false;
|
|
225
|
+
for (const id of variableIds) {
|
|
226
|
+
if (this.data[id]) {
|
|
227
|
+
delete this.data[id];
|
|
228
|
+
this.unreadableSecrets.delete(id);
|
|
229
|
+
changed = true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (changed) await this.save();
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var environmentOverridesStore = new EnvironmentOverridesStore();
|
|
236
|
+
|
|
237
|
+
// ../server/src/services/environment-resolver.ts
|
|
238
|
+
function interpolateValue(raw, envContext) {
|
|
239
|
+
return raw.replace(/\{\{environment\.([^}]+)\}\}/g, (match, key) => {
|
|
240
|
+
return envContext[key] !== void 0 ? envContext[key] : match;
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
function resolveVariable(variable, envContext) {
|
|
244
|
+
const slot = variable.value;
|
|
245
|
+
const finalValue = interpolateValue(slot.pre, envContext) + interpolateValue(slot.value, envContext) + interpolateValue(slot.post, envContext);
|
|
246
|
+
return { value: finalValue, isSecret: slot.isSecret };
|
|
247
|
+
}
|
|
248
|
+
function slotReferenceText(variable) {
|
|
249
|
+
const slot = variable.value;
|
|
250
|
+
return `${slot.pre}
|
|
251
|
+
${slot.value}
|
|
252
|
+
${slot.post}`;
|
|
253
|
+
}
|
|
254
|
+
var HIDDEN_VALUES_NOTICE = `Values shown as ${MASKED_VALUE} are secrets their author did not share with AI. The service does receive them: never treat such a key as missing, and never pass ${MASKED_VALUE} as its value.`;
|
|
255
|
+
function filterEnvForAi(resolved, sharedKeys) {
|
|
256
|
+
const secret = new Set(resolved.envSecrets);
|
|
257
|
+
const shared = new Set(sharedKeys);
|
|
258
|
+
const env = {};
|
|
259
|
+
const envSecrets = [];
|
|
260
|
+
const hiddenKeys = [];
|
|
261
|
+
for (const [key, value] of Object.entries(resolved.env)) {
|
|
262
|
+
const hidden = secret.has(key) && !shared.has(key);
|
|
263
|
+
env[key] = hidden ? MASKED_VALUE : value;
|
|
264
|
+
if (secret.has(key)) envSecrets.push(key);
|
|
265
|
+
if (hidden) hiddenKeys.push(key);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
env,
|
|
269
|
+
envSecrets,
|
|
270
|
+
hiddenKeys,
|
|
271
|
+
...hiddenKeys.length > 0 ? { hiddenNotice: HIDDEN_VALUES_NOTICE } : {}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function describeVariables(environments, ctx) {
|
|
275
|
+
const details = [];
|
|
276
|
+
for (const environment of environments) {
|
|
277
|
+
for (const [variableId, variable] of Object.entries(environment.variables ?? {})) {
|
|
278
|
+
const slot = variable.value;
|
|
279
|
+
const isOverridden = ctx.overriddenVariableIds.has(variableId);
|
|
280
|
+
const isSecret = slot.isSecret || ctx.resolvedSecretKeys.has(variable.key);
|
|
281
|
+
const readableByAi = ctx.maskedKeys.has(variable.key) ? false : !isSecret || isSharedWithAi(slot);
|
|
282
|
+
details.push({
|
|
283
|
+
key: variable.key,
|
|
284
|
+
inService: ctx.serviceKeys.has(variable.key),
|
|
285
|
+
environmentId: environment.id,
|
|
286
|
+
environmentName: environment.name,
|
|
287
|
+
scope: environment.scope,
|
|
288
|
+
...environment.scope === "service" && environment.reference ? { environmentServiceId: environment.reference } : {},
|
|
289
|
+
...slot.description ? { description: slot.description } : {},
|
|
290
|
+
secret: isSecret,
|
|
291
|
+
readableByAi,
|
|
292
|
+
injectedIntoAi: slot.injectIntoAi,
|
|
293
|
+
required: slot.required,
|
|
294
|
+
overrideOnly: slot.overrideOnly,
|
|
295
|
+
overridden: isOverridden,
|
|
296
|
+
...isOverridden && ctx.sharedValues[variable.key] !== void 0 ? { sharedValue: ctx.sharedValues[variable.key] } : {}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return details.sort((a, b) => a.key.localeCompare(b.key) || a.environmentName.localeCompare(b.environmentName));
|
|
301
|
+
}
|
|
302
|
+
function withOverriddenValue(variable, override) {
|
|
303
|
+
return {
|
|
304
|
+
...variable,
|
|
305
|
+
value: {
|
|
306
|
+
...variable.value,
|
|
307
|
+
value: override.value,
|
|
308
|
+
pre: override.pre,
|
|
309
|
+
post: override.post
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
var EnvironmentResolver = class {
|
|
314
|
+
async resolveForService(ctx, opts) {
|
|
315
|
+
if (!ctx.activeEnvironmentId) {
|
|
316
|
+
return { env: {}, envSecrets: [] };
|
|
317
|
+
}
|
|
318
|
+
const serviceEnv = await environmentStore.findServiceEnvironment(ctx.serviceId, ctx.activeEnvironmentId);
|
|
319
|
+
if (!serviceEnv) {
|
|
320
|
+
return this.resolveEnvironment(ctx.activeEnvironmentId, opts);
|
|
321
|
+
}
|
|
322
|
+
return this.resolveEnvironment(serviceEnv.id, opts);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* `applyOverrides: false` rend ce que l'équipe partage, sans ce que ce poste
|
|
326
|
+
* a mis par-dessus : c'est la réponse à « qu'est-ce que ma surcharge
|
|
327
|
+
* remplace ». Le défaut reste `true` — tout le reste du produit veut la
|
|
328
|
+
* valeur avec laquelle le service tourne réellement.
|
|
329
|
+
*/
|
|
330
|
+
async resolveEnvironment(environmentId, opts) {
|
|
331
|
+
const visited = /* @__PURE__ */ new Set();
|
|
332
|
+
const result = await this.resolveDag(environmentId, visited);
|
|
333
|
+
const overridesByKey = opts?.applyOverrides === false ? {} : await this.resolveOverridesByKey(environmentId);
|
|
334
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
335
|
+
const override = overridesByKey[variable.key];
|
|
336
|
+
if (override) {
|
|
337
|
+
result.variables[varId] = withOverriddenValue(variable, override.variable.value);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const rawEnv = {};
|
|
341
|
+
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
342
|
+
const slot = variable.value;
|
|
343
|
+
rawEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
344
|
+
}
|
|
345
|
+
const { ctx: interpolationCtx, secretKeys: ctxSecretKeys } = await this.buildInterpolationContext(environmentId, rawEnv, result.parentEnv, result.parentSecretKeys, result.parentKeySources);
|
|
346
|
+
const secretKeys = this.collectSecretKeys(result.variables, ctxSecretKeys);
|
|
347
|
+
const env = {};
|
|
348
|
+
const envSecrets = [];
|
|
349
|
+
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
350
|
+
const { value, isSecret } = resolveVariable(variable, interpolationCtx);
|
|
351
|
+
env[variable.key] = value;
|
|
352
|
+
if (isSecret || secretKeys.has(variable.key)) {
|
|
353
|
+
envSecrets.push(variable.key);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return { env, envSecrets };
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Resolve an environment (with inheritance) and return only the variables
|
|
360
|
+
* flagged `injectIntoAi`, as a flat KEY→value dict ready to be injected into
|
|
361
|
+
* the process environment of an AI agent spawn.
|
|
362
|
+
*/
|
|
363
|
+
async resolveAiInjectedEnv(environmentId) {
|
|
364
|
+
if (!environmentId) return {};
|
|
365
|
+
const visited = /* @__PURE__ */ new Set();
|
|
366
|
+
const result = await this.resolveDag(environmentId, visited);
|
|
367
|
+
const overridesByKey = await this.resolveOverridesByKey(environmentId);
|
|
368
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
369
|
+
const override = overridesByKey[variable.key];
|
|
370
|
+
if (override) {
|
|
371
|
+
result.variables[varId] = withOverriddenValue(variable, override.variable.value);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const rawEnv = {};
|
|
375
|
+
for (const variable of Object.values(result.variables)) {
|
|
376
|
+
const slot = variable.value;
|
|
377
|
+
rawEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
378
|
+
}
|
|
379
|
+
const { ctx: interpolationCtx } = await this.buildInterpolationContext(
|
|
380
|
+
environmentId,
|
|
381
|
+
rawEnv,
|
|
382
|
+
result.parentEnv,
|
|
383
|
+
result.parentSecretKeys,
|
|
384
|
+
result.parentKeySources
|
|
385
|
+
);
|
|
386
|
+
const injected = {};
|
|
387
|
+
for (const variable of Object.values(result.variables)) {
|
|
388
|
+
if (!variable.value.injectIntoAi) continue;
|
|
389
|
+
const { value } = resolveVariable(variable, interpolationCtx);
|
|
390
|
+
injected[variable.key] = value;
|
|
391
|
+
}
|
|
392
|
+
return injected;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Les variables qu'il faut remplir avant qu'un service de cet environnement
|
|
396
|
+
* puisse démarrer.
|
|
397
|
+
*
|
|
398
|
+
* Deux motifs, et ils ne se mesurent pas au même endroit :
|
|
399
|
+
*
|
|
400
|
+
* - `required` porte sur la valeur **finale**, interpolation et surcharge
|
|
401
|
+
* comprises : une variable obligatoire dont la valeur ne tient qu'à une
|
|
402
|
+
* référence morte est aussi vide qu'une case blanche.
|
|
403
|
+
* - `overrideOnly` porte sur l'**existence d'une surcharge machine**. Elle
|
|
404
|
+
* n'a par construction pas de valeur partagée ; ce qui manque n'est pas un
|
|
405
|
+
* contenu, c'est la réponse de ce poste-ci.
|
|
406
|
+
*
|
|
407
|
+
* Les drapeaux se lisent sur la définition, jamais sur le slot résolu : une
|
|
408
|
+
* surcharge remplace le slot entier et ne porte aucun drapeau — les y lire
|
|
409
|
+
* rendrait toute variable surchargée silencieusement facultative.
|
|
410
|
+
*/
|
|
411
|
+
async findMissingVariables(environmentId) {
|
|
412
|
+
if (!environmentId) return [];
|
|
413
|
+
const [{ env }, definitions, overridesByKey] = await Promise.all([
|
|
414
|
+
this.resolveEnvironment(environmentId),
|
|
415
|
+
this.resolveDag(environmentId, /* @__PURE__ */ new Set()),
|
|
416
|
+
this.resolveOverridesByKey(environmentId)
|
|
417
|
+
]);
|
|
418
|
+
const missing = [];
|
|
419
|
+
for (const [variableId, variable] of Object.entries(definitions.variables)) {
|
|
420
|
+
const slot = variable.value;
|
|
421
|
+
const source = definitions.sources[variableId];
|
|
422
|
+
const base = {
|
|
423
|
+
variableId,
|
|
424
|
+
key: variable.key,
|
|
425
|
+
description: slot.description,
|
|
426
|
+
isSecret: slot.isSecret,
|
|
427
|
+
environmentId: source?.environmentId ?? environmentId,
|
|
428
|
+
environmentName: source?.environmentName ?? ""
|
|
429
|
+
};
|
|
430
|
+
if (slot.overrideOnly && !overridesByKey[variable.key]) {
|
|
431
|
+
missing.push({ ...base, reason: "overrideOnly" });
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (slot.required && (env[variable.key] ?? "") === "") {
|
|
435
|
+
missing.push({ ...base, reason: "required" });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return missing;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Tout ce qu'il reste à remplir sous l'environnement courant.
|
|
442
|
+
*
|
|
443
|
+
* Deux périmètres, et il faut les deux :
|
|
444
|
+
*
|
|
445
|
+
* 1. **L'environnement courant lui-même**, héritage compris —
|
|
446
|
+
* `findMissingVariables` remonte ses `parentIds`. C'est là que vivent les
|
|
447
|
+
* variables globales du projet, et c'est la première branche balayée.
|
|
448
|
+
* 2. **Les environnements de service qui s'activent avec lui**
|
|
449
|
+
* (`activateOn`). Ceux-là ne sont pas des ancêtres du global : ils en
|
|
450
|
+
* héritent, dans l'autre sens. Ne pas y descendre revenait à ne rien
|
|
451
|
+
* réclamer d'un service jusqu'à ce qu'on essaie de le démarrer.
|
|
452
|
+
*
|
|
453
|
+
* Dédoublonné par identifiant de variable : une variable du global remonte
|
|
454
|
+
* dans chaque branche de service qui en hérite, et l'assistant ne doit pas
|
|
455
|
+
* demander deux fois la même chose.
|
|
456
|
+
*/
|
|
457
|
+
async findMissingForActiveEnvironment(globalEnvId) {
|
|
458
|
+
if (!globalEnvId) return [];
|
|
459
|
+
const serviceEnvs = await environmentStore.listByActivateOn(globalEnvId);
|
|
460
|
+
const branches = await Promise.all([
|
|
461
|
+
this.findMissingVariables(globalEnvId),
|
|
462
|
+
...serviceEnvs.map((env) => this.findMissingVariables(env.id))
|
|
463
|
+
]);
|
|
464
|
+
const seen = /* @__PURE__ */ new Set();
|
|
465
|
+
const missing = [];
|
|
466
|
+
for (const branch of branches) {
|
|
467
|
+
for (const item of branch) {
|
|
468
|
+
if (seen.has(item.variableId)) continue;
|
|
469
|
+
seen.add(item.variableId);
|
|
470
|
+
missing.push(item);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return missing;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Les clés que leur auteur a partagées avec les IA, héritage compris.
|
|
477
|
+
*
|
|
478
|
+
* Le partage se lit sur la **définition**, et seulement là : une surcharge
|
|
479
|
+
* machine ne dit que la valeur de ce poste (`withOverriddenValue`). Il n'y a
|
|
480
|
+
* donc qu'un endroit où ce drapeau existe, et rien à réconcilier.
|
|
481
|
+
*
|
|
482
|
+
* Des clés, pas des valeurs : la valeur rendue reste celle que le service
|
|
483
|
+
* reçoit, surcharges et interpolation comprises.
|
|
484
|
+
*/
|
|
485
|
+
async resolveAiSharedKeys(environmentId) {
|
|
486
|
+
if (!environmentId) return /* @__PURE__ */ new Set();
|
|
487
|
+
const result = await this.resolveDag(environmentId, /* @__PURE__ */ new Set());
|
|
488
|
+
const keys = /* @__PURE__ */ new Set();
|
|
489
|
+
for (const variable of Object.values(result.variables)) {
|
|
490
|
+
if (isSharedWithAi(variable.value)) keys.add(variable.key);
|
|
491
|
+
}
|
|
492
|
+
return keys;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Idem, pour ce qu'un service verra réellement : son environnement dédié
|
|
496
|
+
* quand il en a un, l'environnement global sinon — la règle de sélection de
|
|
497
|
+
* `resolveForService`.
|
|
498
|
+
*/
|
|
499
|
+
async resolveAiSharedKeysForService(ctx) {
|
|
500
|
+
if (!ctx.activeEnvironmentId) return /* @__PURE__ */ new Set();
|
|
501
|
+
const serviceEnv = await environmentStore.findServiceEnvironment(ctx.serviceId, ctx.activeEnvironmentId);
|
|
502
|
+
return this.resolveAiSharedKeys(serviceEnv ? serviceEnv.id : ctx.activeEnvironmentId);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Idem, pour ce qu'un service verra réellement : son environnement dédié
|
|
506
|
+
* quand il en a un, l'environnement global sinon. Miroir de
|
|
507
|
+
* `resolveForService`, dont il partage la règle de sélection.
|
|
508
|
+
*/
|
|
509
|
+
async findMissingForService(ctx) {
|
|
510
|
+
if (!ctx.activeEnvironmentId) return [];
|
|
511
|
+
const serviceEnv = await environmentStore.findServiceEnvironment(ctx.serviceId, ctx.activeEnvironmentId);
|
|
512
|
+
return this.findMissingVariables(serviceEnv ? serviceEnv.id : ctx.activeEnvironmentId);
|
|
513
|
+
}
|
|
514
|
+
async resolveEnvironmentWithSources(environmentId) {
|
|
515
|
+
const visited = /* @__PURE__ */ new Set();
|
|
516
|
+
const result = await this.resolveDag(environmentId, visited);
|
|
517
|
+
const overridesByKey = await this.resolveOverridesByKey(environmentId);
|
|
518
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
519
|
+
const override = overridesByKey[variable.key];
|
|
520
|
+
if (override) {
|
|
521
|
+
result.variables[varId] = withOverriddenValue(variable, override.variable.value);
|
|
522
|
+
result.sources[varId] = {
|
|
523
|
+
...result.sources[varId],
|
|
524
|
+
overridden: true,
|
|
525
|
+
...override.source ? { overriddenBy: override.source } : {}
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const rawEnv = {};
|
|
530
|
+
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
531
|
+
const slot = variable.value;
|
|
532
|
+
rawEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
533
|
+
}
|
|
534
|
+
const { ctx: interpolationCtx, secretKeys: ctxSecretKeys, keySources } = await this.buildInterpolationContext(environmentId, rawEnv, result.parentEnv, result.parentSecretKeys, result.parentKeySources);
|
|
535
|
+
const secretKeys = this.collectSecretKeys(result.variables, ctxSecretKeys);
|
|
536
|
+
const env = {};
|
|
537
|
+
const envSecrets = [];
|
|
538
|
+
for (const [_varId, variable] of Object.entries(result.variables)) {
|
|
539
|
+
const { value, isSecret } = resolveVariable(variable, interpolationCtx);
|
|
540
|
+
env[variable.key] = value;
|
|
541
|
+
if (isSecret || secretKeys.has(variable.key)) {
|
|
542
|
+
envSecrets.push(variable.key);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const refPattern = /^\{\{environment\.([^}]+)\}\}$/;
|
|
546
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
547
|
+
const match = variable.value.value.match(refPattern);
|
|
548
|
+
if (match) {
|
|
549
|
+
const refSource = keySources[match[1]];
|
|
550
|
+
if (refSource && result.sources[varId]?.environmentId !== refSource.environmentId) {
|
|
551
|
+
result.sources[varId] = {
|
|
552
|
+
...result.sources[varId],
|
|
553
|
+
resolvedFrom: refSource
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
variables: result.variables,
|
|
560
|
+
env,
|
|
561
|
+
envSecrets,
|
|
562
|
+
sources: result.sources
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
/** Check if a raw value string interpolates any key that is secret in the context */
|
|
566
|
+
/**
|
|
567
|
+
* Toutes les clés dont la valeur est, directement ou par interpolation, un
|
|
568
|
+
* secret.
|
|
569
|
+
*
|
|
570
|
+
* Le contexte d'interpolation ne connaît que les secrets *hérités* : les
|
|
571
|
+
* secrets définis dans le même environnement n'y figurent pas, si bien qu'un
|
|
572
|
+
* `AUTH='Bearer {{environment.TOKEN}}'` voisin de son propre `TOKEN` secret
|
|
573
|
+
* ressortait non marqué — et donc non masqué par toute la chaîne en aval.
|
|
574
|
+
*
|
|
575
|
+
* On part des secrets hérités plus ceux déclarés ici, puis on propage
|
|
576
|
+
* jusqu'au point fixe : une valeur qui référence une clé secrète devient
|
|
577
|
+
* secrète à son tour, y compris en chaîne (A secret → B cite A → C cite B).
|
|
578
|
+
*/
|
|
579
|
+
collectSecretKeys(variables, parentSecretKeys) {
|
|
580
|
+
const secretKeys = new Set(parentSecretKeys);
|
|
581
|
+
for (const variable of Object.values(variables)) {
|
|
582
|
+
if (variable.value.isSecret) secretKeys.add(variable.key);
|
|
583
|
+
}
|
|
584
|
+
let changed = true;
|
|
585
|
+
while (changed) {
|
|
586
|
+
changed = false;
|
|
587
|
+
for (const variable of Object.values(variables)) {
|
|
588
|
+
if (secretKeys.has(variable.key)) continue;
|
|
589
|
+
if (this.interpolatesSecretKey(slotReferenceText(variable), secretKeys)) {
|
|
590
|
+
secretKeys.add(variable.key);
|
|
591
|
+
changed = true;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return secretKeys;
|
|
596
|
+
}
|
|
597
|
+
interpolatesSecretKey(rawValue, ctxSecretKeys) {
|
|
598
|
+
const refs = [...rawValue.matchAll(/\{\{environment\.([^}]+)\}\}/g)].map((m) => m[1]);
|
|
599
|
+
return refs.some((ref) => ctxSecretKeys.has(ref));
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Build the interpolation context for an environment.
|
|
603
|
+
*
|
|
604
|
+
* For inheriting envs (has parents): start from parent concrete values,
|
|
605
|
+
* then overlay with current concrete values. This ensures that
|
|
606
|
+
* {{environment.KEY}} resolves against the parent's value when the child
|
|
607
|
+
* redefines KEY as an interpolation reference.
|
|
608
|
+
*
|
|
609
|
+
* For isolated service envs (scope=service, activateOn set, no parents):
|
|
610
|
+
* the global env is resolved and used as fallback.
|
|
611
|
+
*/
|
|
612
|
+
async buildInterpolationContext(environmentId, rawEnv, parentEnv, parentSecretKeys, parentKeySources) {
|
|
613
|
+
const environment = await environmentStore.get(environmentId);
|
|
614
|
+
if (environment && environment.scope === "service" && environment.activateOn && (!environment.parentIds || environment.parentIds.length === 0)) {
|
|
615
|
+
const globalResolved = await this.resolveEnvironmentWithSources(environment.activateOn);
|
|
616
|
+
const secretKeys = new Set(globalResolved.envSecrets);
|
|
617
|
+
const keySources = {};
|
|
618
|
+
for (const [varId, variable] of Object.entries(globalResolved.variables)) {
|
|
619
|
+
const source = globalResolved.sources[varId];
|
|
620
|
+
if (source) {
|
|
621
|
+
keySources[variable.key] = { environmentId: source.environmentId, environmentName: source.environmentName };
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const ctx2 = { ...globalResolved.env };
|
|
625
|
+
for (const [key, value] of Object.entries(rawEnv)) {
|
|
626
|
+
if (!/\{\{environment\.[^}]+\}\}/.test(value)) {
|
|
627
|
+
ctx2[key] = value;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return { ctx: ctx2, secretKeys, keySources };
|
|
631
|
+
}
|
|
632
|
+
const ctx = { ...parentEnv };
|
|
633
|
+
for (const [key, value] of Object.entries(rawEnv)) {
|
|
634
|
+
if (!/\{\{environment\.[^}]+\}\}/.test(value)) {
|
|
635
|
+
ctx[key] = value;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return { ctx, secretKeys: parentSecretKeys, keySources: parentKeySources };
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* For service-scoped envs, compute effective parentIds by also including
|
|
642
|
+
* the service-scoped envs of ancestor global environments.
|
|
643
|
+
*
|
|
644
|
+
* Example: "Server (test)" has parentIds=[test-global]. test-global has
|
|
645
|
+
* parentIds=[Local-global]. This method adds "Server (Local)" to the
|
|
646
|
+
* effective parents so service-specific variables from ancestor envs
|
|
647
|
+
* are inherited correctly.
|
|
648
|
+
*/
|
|
649
|
+
async getEffectiveParentIds(environment) {
|
|
650
|
+
if (environment.scope !== "service" || !environment.reference || !environment.activateOn) {
|
|
651
|
+
return environment.parentIds;
|
|
652
|
+
}
|
|
653
|
+
const additionalIds = [];
|
|
654
|
+
for (const parentId of environment.parentIds) {
|
|
655
|
+
const parentEnv = await environmentStore.get(parentId);
|
|
656
|
+
if (!parentEnv || parentEnv.scope !== "global") continue;
|
|
657
|
+
for (const grandparentGlobalId of parentEnv.parentIds) {
|
|
658
|
+
const ancestorServiceEnv = await environmentStore.findServiceEnvironment(environment.reference, grandparentGlobalId);
|
|
659
|
+
if (ancestorServiceEnv && !environment.parentIds.includes(ancestorServiceEnv.id)) {
|
|
660
|
+
additionalIds.push(ancestorServiceEnv.id);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (additionalIds.length === 0) return environment.parentIds;
|
|
665
|
+
return [...environment.parentIds, ...additionalIds];
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Project an environment's variables onto their overrides: keep only the
|
|
669
|
+
* variables that have one, and swap in the override's value slot. The key
|
|
670
|
+
* always comes from the definition.
|
|
671
|
+
*
|
|
672
|
+
* Feeding this projection back through `resolveDag` resolves the override
|
|
673
|
+
* chain with the exact same inheritance and dedup-by-key rules as the value
|
|
674
|
+
* chain — so an override set on a parent env is no longer swallowed when a
|
|
675
|
+
* child (or a service env) redeclares the same key.
|
|
676
|
+
*/
|
|
677
|
+
projectOverrides(variables, overrides) {
|
|
678
|
+
const projected = {};
|
|
679
|
+
for (const [varId, variable] of Object.entries(variables)) {
|
|
680
|
+
const override = overrides[varId];
|
|
681
|
+
if (override) {
|
|
682
|
+
projected[varId] = { key: variable.key, value: override.value };
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return projected;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Resolve the override chain independently of the value chain and return the
|
|
689
|
+
* winning override per variable key (empty when nothing is overridden).
|
|
690
|
+
*/
|
|
691
|
+
async resolveOverridesByKey(environmentId) {
|
|
692
|
+
const allOverrides = await environmentOverridesStore.getAll();
|
|
693
|
+
if (Object.keys(allOverrides).length === 0) return {};
|
|
694
|
+
const result = await this.resolveDag(
|
|
695
|
+
environmentId,
|
|
696
|
+
/* @__PURE__ */ new Set(),
|
|
697
|
+
(variables) => this.projectOverrides(variables, allOverrides)
|
|
698
|
+
);
|
|
699
|
+
const byKey = {};
|
|
700
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
701
|
+
const source = result.sources[varId];
|
|
702
|
+
byKey[variable.key] = {
|
|
703
|
+
variable,
|
|
704
|
+
...source ? { source: { environmentId: source.environmentId, environmentName: source.environmentName } } : {}
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return byKey;
|
|
708
|
+
}
|
|
709
|
+
async resolveDag(environmentId, visited, project) {
|
|
710
|
+
if (visited.has(environmentId)) {
|
|
711
|
+
throw new Error(`Circular dependency detected in environment inheritance for ${environmentId}`);
|
|
712
|
+
}
|
|
713
|
+
const environment = await environmentStore.get(environmentId);
|
|
714
|
+
if (!environment) {
|
|
715
|
+
throw new Error(`Environment "${environmentId}" not found`);
|
|
716
|
+
}
|
|
717
|
+
visited.add(environmentId);
|
|
718
|
+
const effectiveParentIds = await this.getEffectiveParentIds(environment);
|
|
719
|
+
const parentResults = [];
|
|
720
|
+
for (const parentId of effectiveParentIds) {
|
|
721
|
+
const parentVisited = new Set(visited);
|
|
722
|
+
const parentResult = await this.resolveDag(parentId, parentVisited, project);
|
|
723
|
+
parentResults.push(parentResult);
|
|
724
|
+
}
|
|
725
|
+
const mergedVariables = {};
|
|
726
|
+
const sources = {};
|
|
727
|
+
for (const result of parentResults) {
|
|
728
|
+
for (const [varId, variable] of Object.entries(result.variables)) {
|
|
729
|
+
for (const [existingId, existingVar] of Object.entries(mergedVariables)) {
|
|
730
|
+
if (existingVar.key === variable.key && existingId !== varId) {
|
|
731
|
+
delete mergedVariables[existingId];
|
|
732
|
+
delete sources[existingId];
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
mergedVariables[varId] = variable;
|
|
736
|
+
sources[varId] = result.sources[varId] ?? sources[varId];
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const parentEnv = {};
|
|
740
|
+
const parentSecretKeys = /* @__PURE__ */ new Set();
|
|
741
|
+
const parentKeySources = {};
|
|
742
|
+
for (const [varId, variable] of Object.entries(mergedVariables)) {
|
|
743
|
+
const slot = variable.value;
|
|
744
|
+
parentEnv[variable.key] = slot.pre + slot.value + slot.post;
|
|
745
|
+
if (slot.isSecret) {
|
|
746
|
+
parentSecretKeys.add(variable.key);
|
|
747
|
+
}
|
|
748
|
+
const source = sources[varId];
|
|
749
|
+
if (source) {
|
|
750
|
+
parentKeySources[variable.key] = { environmentId: source.environmentId, environmentName: source.environmentName };
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const isInterpRef = (v) => /\{\{environment\.[^}]+\}\}/.test(v);
|
|
754
|
+
for (const result of parentResults) {
|
|
755
|
+
for (const [key, value] of Object.entries(result.parentEnv)) {
|
|
756
|
+
const existingIsRef = key in parentEnv && isInterpRef(parentEnv[key]);
|
|
757
|
+
const newIsConcrete = !isInterpRef(value);
|
|
758
|
+
if (!(key in parentEnv) || existingIsRef && newIsConcrete) {
|
|
759
|
+
parentEnv[key] = value;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
for (const key of result.parentSecretKeys) {
|
|
763
|
+
parentSecretKeys.add(key);
|
|
764
|
+
}
|
|
765
|
+
for (const [key, source] of Object.entries(result.parentKeySources)) {
|
|
766
|
+
if (!(key in parentKeySources)) {
|
|
767
|
+
parentKeySources[key] = source;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
for (const key of this.collectSecretKeys(mergedVariables, parentSecretKeys)) {
|
|
772
|
+
parentSecretKeys.add(key);
|
|
773
|
+
}
|
|
774
|
+
const ownVariables = project ? project(environment.variables) : environment.variables;
|
|
775
|
+
for (const [varId, variable] of Object.entries(ownVariables)) {
|
|
776
|
+
let wasInherited = varId in mergedVariables;
|
|
777
|
+
for (const [existingId, existingVar] of Object.entries(mergedVariables)) {
|
|
778
|
+
if (existingVar.key === variable.key && existingId !== varId) {
|
|
779
|
+
delete mergedVariables[existingId];
|
|
780
|
+
delete sources[existingId];
|
|
781
|
+
wasInherited = true;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
mergedVariables[varId] = variable;
|
|
785
|
+
sources[varId] = {
|
|
786
|
+
environmentId: environment.id,
|
|
787
|
+
environmentName: environment.name,
|
|
788
|
+
overridden: wasInherited
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
variables: mergedVariables,
|
|
793
|
+
sources,
|
|
794
|
+
parentEnv,
|
|
795
|
+
parentSecretKeys,
|
|
796
|
+
parentKeySources
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
var environmentResolver = new EnvironmentResolver();
|
|
801
|
+
|
|
802
|
+
export {
|
|
803
|
+
environmentOverridesStore,
|
|
804
|
+
HIDDEN_VALUES_NOTICE,
|
|
805
|
+
filterEnvForAi,
|
|
806
|
+
describeVariables,
|
|
807
|
+
environmentResolver
|
|
808
|
+
};
|
|
809
|
+
//# sourceMappingURL=chunk-WDHLEO5S.js.map
|