Skip to content

Commit 9ff167e

Browse files
committed
Enforce env isolation and add deploy dry-run guards
1 parent a6e2b43 commit 9ff167e

17 files changed

Lines changed: 728 additions & 60 deletions

.github/workflows/bot-deploy.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ name: Bot Deploy
33
on:
44
workflow_dispatch:
55
inputs:
6+
environment:
7+
description: Deployment environment
8+
required: true
9+
default: staging
10+
type: choice
11+
options:
12+
- staging
13+
- production
614
target:
715
description: Deploy target
816
required: true
@@ -32,10 +40,16 @@ on:
3240
required: true
3341
default: false
3442
type: boolean
43+
dry_run:
44+
description: Run deploy checks only (no service mutation/deploy trigger)
45+
required: true
46+
default: false
47+
type: boolean
3548

3649
jobs:
3750
deploy:
3851
runs-on: ubuntu-latest
52+
environment: ${{ github.event.inputs.environment }}
3953
steps:
4054
- name: Checkout
4155
uses: actions/checkout@v4
@@ -53,6 +67,8 @@ jobs:
5367
shell: bash
5468
env:
5569
BOT_SECRETS_ENV_ONLY: "true"
70+
BOT_ENV: ${{ github.event.inputs.environment }}
71+
RENDER_DEPLOY_METADATA_OUT: telegram/out/deploy-render-metadata.${{ github.event.inputs.environment }}.json
5672
RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }}
5773
RENDER_OWNER_ID: ${{ secrets.RENDER_OWNER_ID }}
5874
RENDER_REGION: ${{ secrets.RENDER_REGION }}
@@ -72,6 +88,12 @@ jobs:
7288
FIRECRAWL_API_KEY: ${{ secrets.FIRECRAWL_API_KEY }}
7389
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
7490
DRIFTBOT_API_KEY: ${{ secrets.DRIFTBOT_API_KEY }}
91+
BRAVE_SEARCH_API_KEY: ${{ secrets.BRAVE_SEARCH_API_KEY }}
92+
TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}
93+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
94+
SERPER_API_KEY: ${{ secrets.SERPER_API_KEY }}
95+
SERPAPI_API_KEY: ${{ secrets.SERPAPI_API_KEY }}
96+
GOOGLE_KG_API_KEY: ${{ secrets.GOOGLE_KG_API_KEY }}
7597
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
7698
UNSTRUCTURED_API_KEY: ${{ secrets.UNSTRUCTURED_API_KEY }}
7799
ASSEMBLYAI_API_KEY: ${{ secrets.ASSEMBLYAI_API_KEY }}
@@ -88,6 +110,7 @@ jobs:
88110
set -euo pipefail
89111
npm run secrets:validate:deploy:ci
90112
EXTRA_ARGS=()
113+
EXTRA_ARGS+=(--env "${{ github.event.inputs.environment }}")
91114
EXTRA_ARGS+=(--target "${{ github.event.inputs.target }}")
92115
EXTRA_ARGS+=(--branch "${{ github.event.inputs.branch }}")
93116
EXTRA_ARGS+=(--env-only)
@@ -101,5 +124,8 @@ jobs:
101124
if [[ "${{ github.event.inputs.with_cloudflare_smoke }}" == "true" ]]; then
102125
EXTRA_ARGS+=(--with-cloudflare-smoke)
103126
fi
127+
if [[ "${{ github.event.inputs.dry_run }}" == "true" ]]; then
128+
EXTRA_ARGS+=(--dry-run)
129+
fi
104130
105131
npm run bot:deploy:auto -- "${EXTRA_ARGS[@]}"

.github/workflows/bot-tests.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ name: Bot Tests
33
on:
44
workflow_dispatch:
55
inputs:
6+
environment:
7+
description: Test environment
8+
required: true
9+
default: staging
10+
type: choice
11+
options:
12+
- staging
13+
- production
614
run_real_gemini:
715
description: Run live Gemini tests
816
required: true
@@ -27,6 +35,7 @@ on:
2735
jobs:
2836
test:
2937
runs-on: ubuntu-latest
38+
environment: ${{ github.event.inputs.environment }}
3039
steps:
3140
- name: Checkout
3241
uses: actions/checkout@v4
@@ -44,6 +53,8 @@ jobs:
4453
shell: bash
4554
env:
4655
BOT_SECRETS_ENV_ONLY: "true"
56+
BOT_ENV: ${{ github.event.inputs.environment }}
57+
RENDER_DEPLOY_METADATA_OUT: telegram/out/deploy-render-metadata.${{ github.event.inputs.environment }}.json
4758
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
4859
TELEGRAM_WEBHOOK_SECRET: ${{ secrets.TELEGRAM_WEBHOOK_SECRET }}
4960
TELEGRAM_NOTIFY_CHAT_ID: ${{ secrets.TELEGRAM_NOTIFY_CHAT_ID }}
@@ -60,6 +71,12 @@ jobs:
6071
FIRECRAWL_API_KEY: ${{ secrets.FIRECRAWL_API_KEY }}
6172
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
6273
DRIFTBOT_API_KEY: ${{ secrets.DRIFTBOT_API_KEY }}
74+
BRAVE_SEARCH_API_KEY: ${{ secrets.BRAVE_SEARCH_API_KEY }}
75+
TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }}
76+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
77+
SERPER_API_KEY: ${{ secrets.SERPER_API_KEY }}
78+
SERPAPI_API_KEY: ${{ secrets.SERPAPI_API_KEY }}
79+
GOOGLE_KG_API_KEY: ${{ secrets.GOOGLE_KG_API_KEY }}
6380
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
6481
UNSTRUCTURED_API_KEY: ${{ secrets.UNSTRUCTURED_API_KEY }}
6582
ASSEMBLYAI_API_KEY: ${{ secrets.ASSEMBLYAI_API_KEY }}

engine/src/data/prompt-templates.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@ const STORYBOARD_PROMPT_PREFIX_LINES = [
55

66
const STORYBOARD_RULE_LINES = [
77
'- Keep captions concise, factual, and sequential.',
8+
'- Build a clear narrative arc across panels: setup -> escalation -> turn -> resolution.',
9+
'- Keep character identity and setting continuity stable across panels.',
810
'- Keep each image_prompt visual and concrete for a single panel scene.',
9-
'- Each image prompt should include a description of the scene, characters, and interactions for the panel.',
10-
'- Image prompt should not include requirements for in-image text or panel number.',
11-
'- Each image_prompt must avoid panel numbering.',
12-
'- Each image_prompt must not ask for any text elements in the image.'
11+
'- For each image_prompt include: subject/action, environment, framing angle, lighting mood, and emotional tone.',
12+
'- Image prompt must avoid panel numbering and must never ask for any text elements inside the image.'
1313
];
1414

1515
const STYLE_REFERENCE_PROMPT_LINES = {
16-
intro: 'Create one reference image that defines a consistent visual style for the full comic.',
17-
sceneRule: 'Show key characters and setting mood in one scene.'
16+
intro: 'Create one reference image that defines a consistent visual style bible for the full comic.',
17+
sceneRule: 'Show key characters and setting mood in one scene with strong silhouette readability and cohesive palette.'
1818
};
1919

2020
const PANEL_IMAGE_PROMPT_LINES = {
21-
sceneRule: 'Create one clear scene, no collage.',
21+
sceneRule: 'Create one clear scene (no collage), with strong focal point and readable composition.',
2222
styleLock1: 'STYLE LOCK: a summary reference image is provided as image input.',
2323
styleLock2: 'Treat that summary reference image as the authoritative style guide.',
2424
styleLock3: 'Match its linework, color palette, shading, lighting mood, character rendering, and texture treatment.',

engine/tests/prompts.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('storyboard prompt parsing', () => {
2323
expect(prompt).toContain(`Objective: ${objective}`);
2424
expect(prompt).toContain('Visual style: ink-heavy noir with dynamic framing');
2525
expect(prompt).toContain('must avoid panel numbering');
26-
expect(prompt).toContain('must not ask for any text elements');
26+
expect(prompt).toContain('must never ask for any text elements');
2727
});
2828
});
2929

scripts/deploy-bot-auto.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ function buildRenderDeployArgs(args) {
101101
'openai-key',
102102
'openrouter-key',
103103
'huggingface-token'
104+
,
105+
'env'
104106
];
105107
const out = [];
106108
allowed.forEach((key) => {
@@ -153,6 +155,7 @@ function printSummary(args, targets) {
153155

154156
function main() {
155157
const args = parseArgs(process.argv.slice(2));
158+
const botEnv = String(args.env || process.env.BOT_ENV || 'staging').trim().toLowerCase() || 'staging';
156159
if (Object.prototype.hasOwnProperty.call(args, 'cloudflare-api-token')) {
157160
throw new Error('Deprecated --cloudflare-api-token is not supported. Use --cloudflare-ai-token and --cloudflare-account-api-token.');
158161
}
@@ -171,10 +174,14 @@ function main() {
171174
const renderDeployArgs = buildRenderDeployArgs(args);
172175
runStep('render-deploy', ['run', 'telegram:deploy:auto', '--', ...renderDeployArgs]);
173176
if (shouldRunRenderSanity(args)) {
174-
const sanityArgs = ['run', 'telegram:deploy:sanity'];
175-
const metadataIn = String(args['metadata-in'] || process.env.RENDER_DEPLOY_METADATA_OUT || '').trim();
177+
const sanityArgs = ['run', 'telegram:deploy:sanity', '--', '--env', botEnv];
178+
const metadataIn = String(
179+
args['metadata-in']
180+
|| process.env.RENDER_DEPLOY_METADATA_OUT
181+
|| `telegram/out/deploy-render-metadata.${botEnv}.json`
182+
).trim();
176183
if (metadataIn) {
177-
sanityArgs.push('--', '--metadata-in', metadataIn);
184+
sanityArgs.push('--metadata-in', metadataIn);
178185
}
179186
runStep('render-sanity-e2e', sanityArgs);
180187
}

scripts/fetch_deploy_log.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ const path = require('path');
44
const { loadEnvFiles } = require('../telegram/src/env');
55
const { RenderApiClient } = require('../telegram/scripts/render-api');
66

7+
function resolveBotEnvironment(raw) {
8+
const value = String(raw || '').trim().toLowerCase();
9+
if (!value) return 'staging';
10+
if (value === 'staging' || value === 'stage' || value === 'test') return 'staging';
11+
if (value === 'production' || value === 'prod' || value === 'live') return 'production';
12+
throw new Error(`Invalid --env value '${raw}'. Use staging or production.`);
13+
}
14+
715
function parseArgs(argv) {
816
const out = {};
917
for (let i = 0; i < argv.length; i += 1) {
@@ -106,15 +114,20 @@ function formatLogLine(row) {
106114

107115
async function main() {
108116
const args = parseArgs(process.argv.slice(2));
117+
const botEnv = resolveBotEnvironment(args.env || process.env.BOT_ENV);
109118
const repoRoot = path.resolve(__dirname, '..');
110119
loadEnvFiles([
111120
path.join(repoRoot, '.env.e2e.local'),
112121
path.join(repoRoot, '.env.local'),
113122
path.join(repoRoot, 'telegram/.env')
114123
]);
115124

116-
const metadataPath = path.resolve(args['metadata-in'] || path.join(repoRoot, 'telegram/out/deploy-render-metadata.json'));
125+
const metadataPath = path.resolve(args['metadata-in'] || path.join(repoRoot, `telegram/out/deploy-render-metadata.${botEnv}.json`));
117126
const metadata = readJsonSafe(metadataPath);
127+
const metadataEnv = String(metadata.environment || '').trim().toLowerCase();
128+
if (metadataEnv && metadataEnv !== botEnv) {
129+
throw new Error(`Metadata environment mismatch: expected ${botEnv}, got ${metadataEnv}`);
130+
}
118131

119132
const renderApiKey = firstNonEmpty(args['render-api-key'], process.env.RENDER_API_KEY);
120133
const ownerId = firstNonEmpty(args['owner-id'], process.env.RENDER_OWNER_ID, metadata.ownerId);

scripts/validate-secrets.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,15 @@ const PROFILES = {
5050
'FIRECRAWL_API_KEY',
5151
'JINA_API_KEY',
5252
'DRIFTBOT_API_KEY',
53+
'BRAVE_SEARCH_API_KEY',
54+
'TAVILY_API_KEY',
55+
'EXA_API_KEY',
56+
'SERPER_API_KEY',
57+
'SERPAPI_API_KEY',
58+
'GOOGLE_KG_API_KEY',
5359
'LLAMA_CLOUD_API_KEY',
5460
'UNSTRUCTURED_API_KEY',
61+
'ASSEMBLYAI_API_KEY',
5562
'CLOUDFLARE_ACCOUNT_ID',
5663
'CLOUDFLARE_WORKERS_AI_TOKEN',
5764
'CLOUDFLARE_ACCOUNT_API_TOKEN',
@@ -80,8 +87,15 @@ const PROFILES = {
8087
'FIRECRAWL_API_KEY',
8188
'JINA_API_KEY',
8289
'DRIFTBOT_API_KEY',
90+
'BRAVE_SEARCH_API_KEY',
91+
'TAVILY_API_KEY',
92+
'EXA_API_KEY',
93+
'SERPER_API_KEY',
94+
'SERPAPI_API_KEY',
95+
'GOOGLE_KG_API_KEY',
8396
'LLAMA_CLOUD_API_KEY',
8497
'UNSTRUCTURED_API_KEY',
98+
'ASSEMBLYAI_API_KEY',
8599
'CLOUDFLARE_ACCOUNT_ID',
86100
'CLOUDFLARE_WORKERS_AI_TOKEN',
87101
'CLOUDFLARE_ACCOUNT_API_TOKEN',

0 commit comments

Comments
 (0)