@msn-control/liftoff 0.6.2 → 0.8.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.
- package/README.md +15 -1
- package/assets/governance/single-maintainer-gitflow/policy.md +705 -0
- package/assets/locks/frontend/package-lock.json +1012 -1694
- package/assets/locks/frontend/package.json +5 -6
- package/assets/locks/go-backend/go.mod +17 -0
- package/assets/locks/go-backend/go.sum +159 -0
- package/assets/locks/node-backend/package-lock.json +1473 -2320
- package/assets/locks/node-backend/package.json +8 -7
- package/assets/locks/opentofu-azure/.terraform.lock.hcl +39 -0
- package/assets/locks/opentofu-azure/versions.tf +10 -0
- package/assets/locks/python-genai/function-requirements.txt +1734 -0
- package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/PKG-INFO +25 -0
- package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/SOURCES.txt +6 -0
- package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/dependency_links.txt +1 -0
- package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/requires.txt +23 -0
- package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/top_level.txt +1 -0
- package/assets/locks/python-genai/pyproject.toml +42 -0
- package/assets/locks/python-genai/uv.lock +2536 -0
- package/assets/locks/python-standard/pyproject.toml +36 -0
- package/assets/locks/python-standard/uv.lock +1780 -0
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/catalog.json +4 -4
- package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/package-lock.json +667 -1108
- package/assets/supported-stack.json +711 -0
- package/dist/args.js +26 -1
- package/dist/args.js.map +1 -1
- package/dist/catalogs.d.ts +3 -1
- package/dist/catalogs.js +29 -7
- package/dist/catalogs.js.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +6 -0
- package/dist/commands.js +364 -172
- package/dist/commands.js.map +1 -1
- package/dist/container-validation.d.ts +2 -0
- package/dist/container-validation.js +26 -0
- package/dist/container-validation.js.map +1 -0
- package/dist/file-system.d.ts +1 -0
- package/dist/file-system.js +141 -7
- package/dist/file-system.js.map +1 -1
- package/dist/framework-adapters.js +1 -0
- package/dist/framework-adapters.js.map +1 -1
- package/dist/go-template-assets.d.ts +2 -0
- package/dist/go-template-assets.js +14 -0
- package/dist/go-template-assets.js.map +1 -0
- package/dist/init-filesystem.js +5 -4
- package/dist/init-filesystem.js.map +1 -1
- package/dist/interactive.d.ts +0 -5
- package/dist/interactive.js +11 -73
- package/dist/interactive.js.map +1 -1
- package/dist/opentofu-template-assets.d.ts +2 -0
- package/dist/opentofu-template-assets.js +10 -0
- package/dist/opentofu-template-assets.js.map +1 -0
- package/dist/package-identity.d.ts +8 -0
- package/dist/package-identity.js +16 -0
- package/dist/package-identity.js.map +1 -0
- package/dist/planner.js +42 -3
- package/dist/planner.js.map +1 -1
- package/dist/power-apps-templates.js +6 -0
- package/dist/power-apps-templates.js.map +1 -1
- package/dist/project-dependencies.d.ts +1 -1
- package/dist/project-dependencies.js +15 -34
- package/dist/project-dependencies.js.map +1 -1
- package/dist/published-verifier.js +11 -1
- package/dist/published-verifier.js.map +1 -1
- package/dist/python-template-assets.d.ts +4 -0
- package/dist/python-template-assets.js +34 -0
- package/dist/python-template-assets.js.map +1 -0
- package/dist/repository-governance.d.ts +17 -0
- package/dist/repository-governance.js +507 -0
- package/dist/repository-governance.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +3 -1
- package/dist/runtime.js.map +1 -1
- package/dist/self-upgrade.d.ts +77 -0
- package/dist/self-upgrade.js +499 -0
- package/dist/self-upgrade.js.map +1 -0
- package/dist/stable-release.d.ts +18 -0
- package/dist/stable-release.js +66 -0
- package/dist/stable-release.js.map +1 -0
- package/dist/standard-templates.js +28 -97
- package/dist/standard-templates.js.map +1 -1
- package/dist/supported-stack.d.ts +115 -0
- package/dist/supported-stack.js +439 -0
- package/dist/supported-stack.js.map +1 -0
- package/dist/telemetry/contract.d.ts +1 -1
- package/dist/telemetry/contract.js +1 -0
- package/dist/telemetry/contract.js.map +1 -1
- package/dist/templates.js +216 -125
- package/dist/templates.js.map +1 -1
- package/dist/types.d.ts +18 -1
- package/dist/workstation-catalog.js +15 -12
- package/dist/workstation-catalog.js.map +1 -1
- package/dist/workstation.js +16 -6
- package/dist/workstation.js.map +1 -1
- package/docs/cli-reference.md +99 -22
- package/docs/configuration-and-manifests.md +14 -7
- package/docs/existing-repositories.md +27 -6
- package/docs/getting-started.md +23 -7
- package/docs/prerequisites.md +30 -4
- package/docs/project-structure.md +20 -1
- package/docs/repository-governance.md +91 -0
- package/docs/safety-and-consent.md +42 -17
- package/docs/spec-workflows-and-agents.md +22 -8
- package/docs/supported-stack.md +89 -0
- package/docs/telemetry.md +6 -0
- package/docs/troubleshooting.md +84 -8
- package/docs/workloads.md +18 -5
- package/package.json +14 -6
- package/dist/update-impact.d.ts +0 -23
- package/dist/update-impact.js +0 -127
- package/dist/update-impact.js.map +0 -1
package/dist/templates.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { addGenAiExtensionArtifacts } from './genai-templates.js';
|
|
3
|
+
import { assertImmutableGeneratedContainerReferences } from './container-validation.js';
|
|
3
4
|
import { addPowerAppsCodeAppArtifacts } from './power-apps-templates.js';
|
|
4
5
|
import { addStandardStackArtifacts, renderStandardDockerfile, renderStandardEnv } from './standard-templates.js';
|
|
5
6
|
import { liftoffVersion } from './version.js';
|
|
6
7
|
import { renderNpmLock, renderNpmPackage } from './npm-template-assets.js';
|
|
8
|
+
import { renderOpenTofuProviderLock, renderOpenTofuVersions } from './opentofu-template-assets.js';
|
|
7
9
|
import { formatCommand } from './process-runner.js';
|
|
10
|
+
import { formatContainerImage, supportedStack } from './supported-stack.js';
|
|
11
|
+
import { buildRepositoryGovernanceArtifacts, governancePolicyVersion } from './repository-governance.js';
|
|
12
|
+
import { renderFunctionRequirementsAsset, renderPythonLock, renderPythonPyprojectAsset } from './python-template-assets.js';
|
|
8
13
|
import { workstationRequirementCatalog } from './workstation-catalog.js';
|
|
9
14
|
const contentHash = (content) => `sha256:${createHash('sha256').update(content, 'utf8').digest('hex')}`;
|
|
10
15
|
const DEFAULT_FUNCTION_WORKER_QUEUE_NAME = 'events';
|
|
@@ -77,10 +82,14 @@ export function buildArtifacts(plan) {
|
|
|
77
82
|
addPowerAppsWorkloadArtifacts(add, plan);
|
|
78
83
|
break;
|
|
79
84
|
}
|
|
80
|
-
|
|
85
|
+
addSpecWorkflowArtifacts(add, plan);
|
|
86
|
+
for (const artifact of buildRepositoryGovernanceArtifacts(plan)) {
|
|
87
|
+
add(artifact.logicalName, artifact.category, artifact.pathParts, artifact.content);
|
|
88
|
+
}
|
|
81
89
|
if (plan.workload !== 'power-apps-code-app' && plan.includeFrontend) {
|
|
82
90
|
addFrontendArtifacts(add, plan);
|
|
83
91
|
}
|
|
92
|
+
assertImmutableGeneratedContainerReferences(artifacts);
|
|
84
93
|
const manifest = buildManifest(plan, artifacts);
|
|
85
94
|
artifacts.push({
|
|
86
95
|
logicalName: 'manifest',
|
|
@@ -154,7 +163,7 @@ export function buildManifest(plan, artifacts, options = {}) {
|
|
|
154
163
|
environments: plan.environments.map((environment) => environment.id)
|
|
155
164
|
};
|
|
156
165
|
return {
|
|
157
|
-
artifactVersion:
|
|
166
|
+
artifactVersion: 5,
|
|
158
167
|
generatedBy: 'Mission Control Liftoff',
|
|
159
168
|
liftoffVersion,
|
|
160
169
|
project: {
|
|
@@ -169,6 +178,16 @@ export function buildManifest(plan, artifacts, options = {}) {
|
|
|
169
178
|
adapter: plan.framework.id,
|
|
170
179
|
...(frameworkState === 'initialized' ? { contractVersion: plan.framework.version } : {})
|
|
171
180
|
},
|
|
181
|
+
governance: plan.governanceProfile.id === 'none'
|
|
182
|
+
? {
|
|
183
|
+
profile: 'none',
|
|
184
|
+
state: 'disabled'
|
|
185
|
+
}
|
|
186
|
+
: {
|
|
187
|
+
profile: plan.governanceProfile.id,
|
|
188
|
+
policyVersion: governancePolicyVersion,
|
|
189
|
+
state: 'handoff-generated'
|
|
190
|
+
},
|
|
172
191
|
artifacts: artifacts
|
|
173
192
|
.filter((artifact) => artifact.category !== 'seed' && artifact.category !== 'framework')
|
|
174
193
|
.map((artifact) => ({
|
|
@@ -193,7 +212,8 @@ function addBaseArtifacts(add, plan) {
|
|
|
193
212
|
environments: plan.environments.map((environment) => environment.id),
|
|
194
213
|
specWorkflow: plan.specWorkflow.id,
|
|
195
214
|
agents: plan.agents.map((agent) => agent.id),
|
|
196
|
-
...(plan.defaultAgent ? { defaultAgent: plan.defaultAgent.id } : {})
|
|
215
|
+
...(plan.defaultAgent ? { defaultAgent: plan.defaultAgent.id } : {}),
|
|
216
|
+
governanceProfile: plan.governanceProfile.id
|
|
197
217
|
}, null, 2));
|
|
198
218
|
add('env-example', 'configuration', ['.env.example'], renderEnvExample(plan));
|
|
199
219
|
add('backend-dockerfile', 'runtime', ['Dockerfile'], plan.workload === 'genai' ? renderBackendDockerfile() : renderStandardDockerfile(plan));
|
|
@@ -205,12 +225,14 @@ function addPowerAppsProjectConfig(add, plan) {
|
|
|
205
225
|
specWorkflow: plan.specWorkflow.id,
|
|
206
226
|
agents: plan.agents.map((agent) => agent.id),
|
|
207
227
|
...(plan.defaultAgent ? { defaultAgent: plan.defaultAgent.id } : {}),
|
|
208
|
-
codeAppsPlugin: plan.codeAppsPlugin
|
|
228
|
+
codeAppsPlugin: plan.codeAppsPlugin,
|
|
229
|
+
governanceProfile: plan.governanceProfile.id
|
|
209
230
|
}, null, 2));
|
|
210
231
|
}
|
|
211
232
|
function addBackendArtifacts(add, plan) {
|
|
212
233
|
const routeModule = pyModule(genAiPattern(plan).id);
|
|
213
234
|
add('backend-pyproject', 'backend', ['backend', 'pyproject.toml'], renderBackendPyproject(plan));
|
|
235
|
+
add('backend-uv-lock', 'backend', ['backend', 'uv.lock'], renderPythonLock('genai', `${plan.safeProjectName}-backend`));
|
|
214
236
|
add('backend-package', 'backend', ['backend', '__init__.py'], '');
|
|
215
237
|
add('backend-api-package', 'backend', ['backend', 'apis', '__init__.py'], '');
|
|
216
238
|
add('backend-main', 'backend', ['backend', 'apis', 'main.py'], renderFastApiMain(plan, routeModule));
|
|
@@ -285,7 +307,8 @@ function addDockerArtifacts(add, plan) {
|
|
|
285
307
|
}
|
|
286
308
|
function addInfrastructureArtifacts(add, plan) {
|
|
287
309
|
const base = ['infrastructure', 'opentofu', 'azure'];
|
|
288
|
-
add('opentofu-versions', 'infrastructure', [...base, 'versions.tf'],
|
|
310
|
+
add('opentofu-versions', 'infrastructure', [...base, 'versions.tf'], renderOpenTofuVersions());
|
|
311
|
+
add('opentofu-provider-lock', 'infrastructure', [...base, '.terraform.lock.hcl'], renderOpenTofuProviderLock());
|
|
289
312
|
add('opentofu-providers', 'infrastructure', [...base, 'providers.tf'], renderTofuProviders());
|
|
290
313
|
add('opentofu-variables', 'infrastructure', [...base, 'variables.tf'], renderTofuVariables(plan));
|
|
291
314
|
add('opentofu-main', 'infrastructure', [...base, 'main.tf'], renderTofuMain(plan));
|
|
@@ -297,7 +320,7 @@ function addInfrastructureArtifacts(add, plan) {
|
|
|
297
320
|
add(`opentofu-${environment.id}-tfvars`, 'infrastructure', [...base, 'environments', `${environment.id}.tfvars`], renderTofuTfvars(plan, environment.id));
|
|
298
321
|
}
|
|
299
322
|
}
|
|
300
|
-
function
|
|
323
|
+
function addSpecWorkflowArtifacts(add, plan) {
|
|
301
324
|
if (plan.workload === 'power-apps-code-app') {
|
|
302
325
|
if (plan.specWorkflow.id === 'openspec') {
|
|
303
326
|
const changeName = `bootstrap-${plan.safeProjectName}`;
|
|
@@ -425,10 +448,11 @@ function addFrontendArtifacts(add, plan) {
|
|
|
425
448
|
function renderDirectBuildAndTestGuide(plan) {
|
|
426
449
|
let backendCommands;
|
|
427
450
|
if (plan.workload === 'genai' || plan.apiStack.id === 'python-fastapi') {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
451
|
+
const extras = plan.workload === 'genai' && hasFunctionWorker(plan)
|
|
452
|
+
? ' --extra functions'
|
|
453
|
+
: '';
|
|
454
|
+
backendCommands = `uv sync --frozen --project backend --extra test${extras}
|
|
455
|
+
uv run --project backend python -m pytest -q backend/tests`;
|
|
432
456
|
}
|
|
433
457
|
else if (plan.apiStack.id === 'node-fastify') {
|
|
434
458
|
backendCommands = `cd backend
|
|
@@ -452,24 +476,12 @@ npm ci
|
|
|
452
476
|
npm run build
|
|
453
477
|
\`\`\`
|
|
454
478
|
` : '';
|
|
455
|
-
const windowsPythonCommands = plan.workload === 'genai' || plan.apiStack.id === 'python-fastapi'
|
|
456
|
-
? `
|
|
457
|
-
|
|
458
|
-
Equivalent Windows dependency commands:
|
|
459
|
-
|
|
460
|
-
\`\`\`powershell
|
|
461
|
-
py -3 -m venv .venv
|
|
462
|
-
.venv\\Scripts\\python.exe -m pip install -e "./backend[test]"
|
|
463
|
-
\`\`\`
|
|
464
|
-
`
|
|
465
|
-
: '';
|
|
466
479
|
const functionCommands = plan.workload === 'genai' && hasFunctionWorker(plan) ? `
|
|
467
480
|
|
|
468
|
-
Run the Function worker unit tests from the same Python
|
|
481
|
+
Run the Function worker unit tests from the same locked Python environment:
|
|
469
482
|
|
|
470
483
|
\`\`\`bash
|
|
471
|
-
|
|
472
|
-
(cd functions/${functionWorkerName(plan)} && ../../.venv/bin/python -m pytest -q)
|
|
484
|
+
(cd functions/${functionWorkerName(plan)} && uv run --project ../../backend --directory . python -m pytest -q)
|
|
473
485
|
\`\`\`
|
|
474
486
|
` : '';
|
|
475
487
|
return `## Project Dependencies, Build, And Test
|
|
@@ -480,7 +492,7 @@ Run the Function worker unit tests from the same Python virtual environment:
|
|
|
480
492
|
${backendCommands}
|
|
481
493
|
\`\`\`
|
|
482
494
|
|
|
483
|
-
${
|
|
495
|
+
${frontendCommands}${functionCommands}`;
|
|
484
496
|
}
|
|
485
497
|
function renderAdvisoryReadinessGuide(plan) {
|
|
486
498
|
const selected = [
|
|
@@ -553,9 +565,13 @@ Copy \`.env.example\` to \`.env\`, then configure only the integrations you use:
|
|
|
553
565
|
function renderGeneratedUpdateGuide() {
|
|
554
566
|
return `## Safe Liftoff Updates
|
|
555
567
|
|
|
556
|
-
\`liftoff
|
|
568
|
+
\`liftoff upgrade\` replaces a supported global Liftoff CLI installation; it does not inspect or modify this project. Check and apply CLI replacement separately with \`liftoff upgrade --check\` and \`liftoff upgrade\`.
|
|
569
|
+
|
|
570
|
+
\`single-maintainer-gitflow\` repository governance generates a local handoff only. Review \`.liftoff/governance/README.md\`, commit and push the project, then run a selected-agent launcher for read-only Phase 0. Live enforcement requires a separately approved governance change.
|
|
571
|
+
|
|
572
|
+
\`liftoff update\` immediately applies safe managed changes without prompting in interactive, redirected, and automated environments. Use \`liftoff update --check\` for a read-only human drift report, or \`liftoff update --check --json\` as an automation gate that exits 0 when clean and 2 when drift exists.
|
|
557
573
|
|
|
558
|
-
An occupied destination with different user bytes
|
|
574
|
+
Local or user-owned conflicts are skipped by default. Review the reported paths, commit or copy local work, and use \`liftoff update --force\` only when every listed overwrite is intended. An occupied destination with different user bytes remains preserved without force, while an identical destination is adopted without rewriting it. Update never deletes orphans or installs dependencies. A failed transaction is rolled back, but Liftoff retains no backup after a successful overwrite.
|
|
559
575
|
|
|
560
576
|
Liftoff rejects malformed, traversal, absolute, drive-qualified, UNC, separator-containing, or symlink-escaping manifest paths before artifact access. If the manifest is unsafe or malformed, restore \`liftoff.manifest.json\` from version control or regenerate the project with a matching Liftoff version; do not hand-edit unsafe paths. Run \`liftoff <command> --help\` for command-specific syntax because unknown flags, subcommands, values, and extra arguments fail before any write.
|
|
561
577
|
`;
|
|
@@ -694,16 +710,26 @@ LANGFUSE_SECRET_KEY=
|
|
|
694
710
|
`;
|
|
695
711
|
}
|
|
696
712
|
function renderBackendDockerfile() {
|
|
697
|
-
return `FROM
|
|
713
|
+
return `FROM ${formatContainerImage(supportedStack.containers['uv-tool'])} AS uv
|
|
714
|
+
FROM ${formatContainerImage(supportedStack.containers['python-runtime'])}
|
|
698
715
|
|
|
699
716
|
WORKDIR /app
|
|
700
717
|
|
|
701
718
|
ENV PYTHONDONTWRITEBYTECODE=1
|
|
702
719
|
ENV PYTHONUNBUFFERED=1
|
|
703
720
|
ENV PYTHONPATH=/app
|
|
721
|
+
ENV PATH=/app/backend/.venv/bin:$PATH
|
|
722
|
+
COPY --from=uv /uv /uvx /bin/
|
|
704
723
|
|
|
724
|
+
ARG UV_DEFAULT_INDEX=https://pypi.org/simple
|
|
705
725
|
COPY backend/pyproject.toml /app/backend/pyproject.toml
|
|
706
|
-
|
|
726
|
+
COPY backend/uv.lock /app/backend/uv.lock
|
|
727
|
+
RUN --mount=type=cache,target=/root/.cache/uv \\
|
|
728
|
+
uv export --frozen --no-dev --no-emit-project --project /app/backend --output-file /tmp/requirements.txt \\
|
|
729
|
+
&& uv venv /app/backend/.venv \\
|
|
730
|
+
&& uv pip install --python /app/backend/.venv/bin/python --require-hashes \\
|
|
731
|
+
--default-index "$UV_DEFAULT_INDEX" --requirements /tmp/requirements.txt \\
|
|
732
|
+
&& rm /tmp/requirements.txt
|
|
707
733
|
|
|
708
734
|
COPY backend /app/backend
|
|
709
735
|
COPY database /app/database
|
|
@@ -713,43 +739,7 @@ CMD ["uvicorn", "backend.apis.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
|
713
739
|
`;
|
|
714
740
|
}
|
|
715
741
|
function renderBackendPyproject(plan) {
|
|
716
|
-
return `
|
|
717
|
-
name = "${plan.safeProjectName}-backend"
|
|
718
|
-
version = "0.1.0"
|
|
719
|
-
requires-python = ">=3.12"
|
|
720
|
-
dependencies = [
|
|
721
|
-
"fastapi>=0.111",
|
|
722
|
-
"uvicorn[standard]>=0.30",
|
|
723
|
-
"pydantic>=2.7",
|
|
724
|
-
"pydantic-settings>=2.3",
|
|
725
|
-
"pydantic-ai-slim[openai]==1.107.1",
|
|
726
|
-
"scalar-fastapi>=1.0",
|
|
727
|
-
"sqlalchemy[asyncio]>=2.0",
|
|
728
|
-
"asyncpg>=0.29",
|
|
729
|
-
"psycopg[binary]>=3.2",
|
|
730
|
-
"alembic>=1.13",
|
|
731
|
-
"redis>=5.0",
|
|
732
|
-
"langfuse==2.60.10",
|
|
733
|
-
"azure-servicebus>=7.12",
|
|
734
|
-
"azure-identity>=1.17",
|
|
735
|
-
"azure-storage-blob>=12.20",
|
|
736
|
-
"azure-communication-email>=1.0"
|
|
737
|
-
]
|
|
738
|
-
|
|
739
|
-
[project.optional-dependencies]
|
|
740
|
-
test = ["pytest>=8.2", "httpx>=0.27"]
|
|
741
|
-
|
|
742
|
-
[build-system]
|
|
743
|
-
requires = ["setuptools>=70"]
|
|
744
|
-
build-backend = "setuptools.build_meta"
|
|
745
|
-
|
|
746
|
-
[tool.setuptools]
|
|
747
|
-
packages = []
|
|
748
|
-
|
|
749
|
-
[tool.pytest.ini_options]
|
|
750
|
-
pythonpath = [".."]
|
|
751
|
-
testpaths = ["tests"]
|
|
752
|
-
`;
|
|
742
|
+
return renderPythonPyprojectAsset('genai', `${plan.safeProjectName}-backend`);
|
|
753
743
|
}
|
|
754
744
|
function renderFastApiMain(plan, routeModule) {
|
|
755
745
|
return `from fastapi import FastAPI
|
|
@@ -1136,8 +1126,12 @@ class LangfuseTracer:
|
|
|
1136
1126
|
|
|
1137
1127
|
@asynccontextmanager
|
|
1138
1128
|
async def trace(self, name: str, input_data: Any = None):
|
|
1139
|
-
remote_trace = self._client.
|
|
1140
|
-
|
|
1129
|
+
remote_trace = self._client.start_observation(
|
|
1130
|
+
name=name,
|
|
1131
|
+
as_type="span",
|
|
1132
|
+
input=input_data,
|
|
1133
|
+
)
|
|
1134
|
+
remote_id = getattr(remote_trace, "trace_id", None)
|
|
1141
1135
|
handle = TraceHandle(
|
|
1142
1136
|
enabled=True,
|
|
1143
1137
|
trace_id=str(remote_id) if remote_id is not None else None,
|
|
@@ -1149,6 +1143,8 @@ class LangfuseTracer:
|
|
|
1149
1143
|
raise
|
|
1150
1144
|
else:
|
|
1151
1145
|
remote_trace.update(output=handle.output)
|
|
1146
|
+
finally:
|
|
1147
|
+
remote_trace.end()
|
|
1152
1148
|
|
|
1153
1149
|
|
|
1154
1150
|
def build_tracer(
|
|
@@ -1293,21 +1289,25 @@ from backend.observability.tracing import (
|
|
|
1293
1289
|
|
|
1294
1290
|
|
|
1295
1291
|
class FakeRemoteTrace:
|
|
1296
|
-
|
|
1292
|
+
trace_id = "trace-123"
|
|
1297
1293
|
|
|
1298
1294
|
def __init__(self):
|
|
1299
1295
|
self.updates = []
|
|
1296
|
+
self.ended = False
|
|
1300
1297
|
|
|
1301
1298
|
def update(self, **values):
|
|
1302
1299
|
self.updates.append(values)
|
|
1303
1300
|
|
|
1301
|
+
def end(self):
|
|
1302
|
+
self.ended = True
|
|
1303
|
+
|
|
1304
1304
|
|
|
1305
1305
|
class FakeLangfuse:
|
|
1306
1306
|
def __init__(self):
|
|
1307
1307
|
self.calls = []
|
|
1308
1308
|
self.remote_trace = FakeRemoteTrace()
|
|
1309
1309
|
|
|
1310
|
-
def
|
|
1310
|
+
def start_observation(self, **values):
|
|
1311
1311
|
self.calls.append(values)
|
|
1312
1312
|
return self.remote_trace
|
|
1313
1313
|
|
|
@@ -1337,8 +1337,11 @@ def test_configured_tracing_updates_langfuse_operation():
|
|
|
1337
1337
|
trace.set_output({"answer": "world"})
|
|
1338
1338
|
|
|
1339
1339
|
asyncio.run(scenario())
|
|
1340
|
-
assert client.calls == [
|
|
1340
|
+
assert client.calls == [
|
|
1341
|
+
{"name": "agent.run", "as_type": "span", "input": {"prompt": "hello"}}
|
|
1342
|
+
]
|
|
1341
1343
|
assert client.remote_trace.updates == [{"output": {"answer": "world"}}]
|
|
1344
|
+
assert client.remote_trace.ended is True
|
|
1342
1345
|
|
|
1343
1346
|
|
|
1344
1347
|
def test_partial_langfuse_configuration_fails(monkeypatch):
|
|
@@ -1712,15 +1715,11 @@ Deployed triggers use \`ServiceBusConnection__fullyQualifiedNamespace\` and \`Se
|
|
|
1712
1715
|
## Local Development
|
|
1713
1716
|
|
|
1714
1717
|
\`\`\`bash
|
|
1715
|
-
|
|
1716
|
-
source .venv/bin/activate
|
|
1717
|
-
pip install -r requirements.txt
|
|
1718
|
+
uv sync --frozen --project ../../backend --extra test --extra functions
|
|
1718
1719
|
cp local.settings.example.json local.settings.json
|
|
1719
|
-
python -m pytest -q
|
|
1720
|
+
uv run --project ../../backend --directory . python -m pytest -q
|
|
1720
1721
|
func start
|
|
1721
1722
|
\`\`\`
|
|
1722
|
-
|
|
1723
|
-
On Windows, activate the virtual environment with \`.venv\\Scripts\\activate\`.
|
|
1724
1723
|
`;
|
|
1725
1724
|
}
|
|
1726
1725
|
function renderFunctionHostJson() {
|
|
@@ -1747,9 +1746,7 @@ function renderFunctionLocalSettings(plan) {
|
|
|
1747
1746
|
}, null, 2);
|
|
1748
1747
|
}
|
|
1749
1748
|
function renderFunctionRequirements() {
|
|
1750
|
-
return
|
|
1751
|
-
pytest>=8.2
|
|
1752
|
-
`;
|
|
1749
|
+
return renderFunctionRequirementsAsset();
|
|
1753
1750
|
}
|
|
1754
1751
|
function renderFunctionApp(plan) {
|
|
1755
1752
|
const pattern = genAiPattern(plan);
|
|
@@ -1861,7 +1858,9 @@ function renderDockerCompose(plan) {
|
|
|
1861
1858
|
depends_on:
|
|
1862
1859
|
- backend
|
|
1863
1860
|
` : '';
|
|
1864
|
-
const postgresImage = plan.workload === 'genai'
|
|
1861
|
+
const postgresImage = plan.workload === 'genai'
|
|
1862
|
+
? formatContainerImage(supportedStack.containers.pgvector)
|
|
1863
|
+
: formatContainerImage(supportedStack.containers.postgres);
|
|
1865
1864
|
return `services:
|
|
1866
1865
|
backend:
|
|
1867
1866
|
build:
|
|
@@ -1888,46 +1887,136 @@ ${frontendService}
|
|
|
1888
1887
|
POSTGRES_DB: ${plan.safeProjectName.replace(/-/g, '_')}
|
|
1889
1888
|
ports:
|
|
1890
1889
|
- "5432:5432"
|
|
1890
|
+
healthcheck:
|
|
1891
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
1892
|
+
interval: 3s
|
|
1893
|
+
timeout: 3s
|
|
1894
|
+
retries: 10
|
|
1891
1895
|
|
|
1892
1896
|
redis:
|
|
1893
|
-
image: redis
|
|
1897
|
+
image: ${formatContainerImage(supportedStack.containers.redis)}
|
|
1894
1898
|
ports:
|
|
1895
1899
|
- "6379:6379"
|
|
1896
1900
|
|
|
1897
1901
|
azurite:
|
|
1898
|
-
image:
|
|
1902
|
+
image: ${formatContainerImage(supportedStack.containers.azurite)}
|
|
1899
1903
|
command: azurite --blobHost 0.0.0.0
|
|
1900
1904
|
ports:
|
|
1901
1905
|
- "10000:10000"
|
|
1902
1906
|
|
|
1903
1907
|
mailpit:
|
|
1904
|
-
image:
|
|
1908
|
+
image: ${formatContainerImage(supportedStack.containers.mailpit)}
|
|
1905
1909
|
ports:
|
|
1906
1910
|
- "8025:8025"
|
|
1907
1911
|
|
|
1908
|
-
${plan.workload === 'genai' ? ` langfuse:
|
|
1909
|
-
image: langfuse
|
|
1912
|
+
${plan.workload === 'genai' ? ` langfuse-worker:
|
|
1913
|
+
image: ${formatContainerImage(supportedStack.containers['langfuse-worker'])}
|
|
1910
1914
|
profiles:
|
|
1911
1915
|
- observability
|
|
1912
|
-
|
|
1916
|
+
depends_on: &langfuse-dependencies
|
|
1917
|
+
postgres:
|
|
1918
|
+
condition: service_healthy
|
|
1919
|
+
langfuse-redis:
|
|
1920
|
+
condition: service_healthy
|
|
1921
|
+
clickhouse:
|
|
1922
|
+
condition: service_healthy
|
|
1923
|
+
minio:
|
|
1924
|
+
condition: service_healthy
|
|
1925
|
+
environment: &langfuse-environment
|
|
1913
1926
|
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/${plan.safeProjectName.replace(/-/g, '_')}
|
|
1914
|
-
|
|
1915
|
-
SALT: local-development-
|
|
1927
|
+
NEXTAUTH_URL: http://localhost:3000
|
|
1928
|
+
SALT: local-development-salt
|
|
1929
|
+
ENCRYPTION_KEY: 0000000000000000000000000000000000000000000000000000000000000000
|
|
1930
|
+
TELEMETRY_ENABLED: "false"
|
|
1931
|
+
CLICKHOUSE_MIGRATION_URL: clickhouse://clickhouse:9000
|
|
1932
|
+
CLICKHOUSE_URL: http://clickhouse:8123
|
|
1933
|
+
CLICKHOUSE_USER: clickhouse
|
|
1934
|
+
CLICKHOUSE_PASSWORD: clickhouse
|
|
1935
|
+
REDIS_HOST: langfuse-redis
|
|
1936
|
+
REDIS_PORT: 6379
|
|
1937
|
+
REDIS_AUTH: langfuse-redis
|
|
1938
|
+
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: langfuse
|
|
1939
|
+
LANGFUSE_S3_EVENT_UPLOAD_REGION: auto
|
|
1940
|
+
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: minio
|
|
1941
|
+
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: miniosecret
|
|
1942
|
+
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: http://minio:9000
|
|
1943
|
+
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: "true"
|
|
1944
|
+
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: langfuse
|
|
1945
|
+
LANGFUSE_S3_MEDIA_UPLOAD_REGION: auto
|
|
1946
|
+
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID: minio
|
|
1947
|
+
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: miniosecret
|
|
1948
|
+
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: http://minio:9000
|
|
1949
|
+
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: "true"
|
|
1950
|
+
|
|
1951
|
+
langfuse:
|
|
1952
|
+
image: ${formatContainerImage(supportedStack.containers['langfuse-web'])}
|
|
1953
|
+
profiles:
|
|
1954
|
+
- observability
|
|
1955
|
+
depends_on: *langfuse-dependencies
|
|
1956
|
+
environment:
|
|
1957
|
+
<<: *langfuse-environment
|
|
1958
|
+
NEXTAUTH_SECRET: local-development-secret
|
|
1959
|
+
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: http://localhost:9090
|
|
1916
1960
|
ports:
|
|
1917
1961
|
- "3000:3000"
|
|
1962
|
+
|
|
1963
|
+
clickhouse:
|
|
1964
|
+
image: ${formatContainerImage(supportedStack.containers.clickhouse)}
|
|
1965
|
+
profiles:
|
|
1966
|
+
- observability
|
|
1967
|
+
user: "101:101"
|
|
1968
|
+
environment:
|
|
1969
|
+
CLICKHOUSE_DB: default
|
|
1970
|
+
CLICKHOUSE_USER: clickhouse
|
|
1971
|
+
CLICKHOUSE_PASSWORD: clickhouse
|
|
1972
|
+
volumes:
|
|
1973
|
+
- langfuse-clickhouse-data:/var/lib/clickhouse
|
|
1974
|
+
- langfuse-clickhouse-logs:/var/log/clickhouse-server
|
|
1975
|
+
healthcheck:
|
|
1976
|
+
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1"]
|
|
1977
|
+
interval: 5s
|
|
1978
|
+
timeout: 5s
|
|
1979
|
+
retries: 10
|
|
1980
|
+
|
|
1981
|
+
minio:
|
|
1982
|
+
image: ${formatContainerImage(supportedStack.containers.minio)}
|
|
1983
|
+
profiles:
|
|
1984
|
+
- observability
|
|
1985
|
+
entrypoint: sh
|
|
1986
|
+
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
|
|
1987
|
+
environment:
|
|
1988
|
+
MINIO_ROOT_USER: minio
|
|
1989
|
+
MINIO_ROOT_PASSWORD: miniosecret
|
|
1990
|
+
ports:
|
|
1991
|
+
- "9090:9000"
|
|
1992
|
+
- "127.0.0.1:9091:9001"
|
|
1993
|
+
volumes:
|
|
1994
|
+
- langfuse-minio-data:/data
|
|
1995
|
+
healthcheck:
|
|
1996
|
+
test: ["CMD", "mc", "ready", "local"]
|
|
1997
|
+
interval: 1s
|
|
1998
|
+
timeout: 5s
|
|
1999
|
+
retries: 5
|
|
2000
|
+
|
|
2001
|
+
langfuse-redis:
|
|
2002
|
+
image: ${formatContainerImage(supportedStack.containers.redis)}
|
|
2003
|
+
profiles:
|
|
2004
|
+
- observability
|
|
2005
|
+
command: ["redis-server", "--requirepass", "langfuse-redis", "--maxmemory-policy", "noeviction"]
|
|
2006
|
+
volumes:
|
|
2007
|
+
- langfuse-redis-data:/data
|
|
2008
|
+
healthcheck:
|
|
2009
|
+
test: ["CMD", "redis-cli", "-a", "langfuse-redis", "ping"]
|
|
2010
|
+
interval: 3s
|
|
2011
|
+
timeout: 10s
|
|
2012
|
+
retries: 10
|
|
2013
|
+
` : ''}
|
|
2014
|
+
${plan.workload === 'genai' ? `volumes:
|
|
2015
|
+
langfuse-clickhouse-data:
|
|
2016
|
+
langfuse-clickhouse-logs:
|
|
2017
|
+
langfuse-minio-data:
|
|
2018
|
+
langfuse-redis-data:
|
|
1918
2019
|
` : ''}
|
|
1919
|
-
`;
|
|
1920
|
-
}
|
|
1921
|
-
function renderTofuVersions() {
|
|
1922
|
-
return `terraform {
|
|
1923
|
-
required_version = ">= 1.6.0"
|
|
1924
|
-
required_providers {
|
|
1925
|
-
azurerm = {
|
|
1926
|
-
source = "hashicorp/azurerm"
|
|
1927
|
-
version = "~> 3.110"
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
2020
|
`;
|
|
1932
2021
|
}
|
|
1933
2022
|
function renderTofuProviders() {
|
|
@@ -1940,7 +2029,7 @@ function renderTofuVariables(plan) {
|
|
|
1940
2029
|
const frontendVariables = plan.includeFrontend ? `
|
|
1941
2030
|
variable "frontend_image" {
|
|
1942
2031
|
type = string
|
|
1943
|
-
default = "
|
|
2032
|
+
default = "${formatContainerImage(supportedStack.containers['container-apps-bootstrap'])}"
|
|
1944
2033
|
description = "Frontend image. Replace the bootstrap image with the generated frontend image after pushing it to ACR."
|
|
1945
2034
|
}
|
|
1946
2035
|
` : '';
|
|
@@ -1953,7 +2042,7 @@ variable "function_worker_queue_name" {
|
|
|
1953
2042
|
|
|
1954
2043
|
variable "functions_python_version" {
|
|
1955
2044
|
type = string
|
|
1956
|
-
default = "
|
|
2045
|
+
default = "${supportedStack.runtimes.python.releaseLine}"
|
|
1957
2046
|
description = "Python runtime version for the generated Azure Functions worker."
|
|
1958
2047
|
}
|
|
1959
2048
|
` : '';
|
|
@@ -1980,7 +2069,7 @@ variable "resource_suffix" {
|
|
|
1980
2069
|
|
|
1981
2070
|
variable "backend_image" {
|
|
1982
2071
|
type = string
|
|
1983
|
-
default = "
|
|
2072
|
+
default = "${formatContainerImage(supportedStack.containers['container-apps-bootstrap'])}"
|
|
1984
2073
|
description = "Backend image. Replace the bootstrap image with the generated backend image after pushing it to ACR."
|
|
1985
2074
|
}
|
|
1986
2075
|
|
|
@@ -2281,7 +2370,7 @@ resource "azurerm_storage_account" "main" {
|
|
|
2281
2370
|
|
|
2282
2371
|
resource "azurerm_storage_container" "documents" {
|
|
2283
2372
|
name = "documents"
|
|
2284
|
-
|
|
2373
|
+
storage_account_id = azurerm_storage_account.main.id
|
|
2285
2374
|
container_access_type = "private"
|
|
2286
2375
|
}
|
|
2287
2376
|
|
|
@@ -2305,11 +2394,12 @@ resource "azurerm_communication_service" "main" {
|
|
|
2305
2394
|
}
|
|
2306
2395
|
|
|
2307
2396
|
resource "azurerm_key_vault" "main" {
|
|
2308
|
-
name
|
|
2309
|
-
location
|
|
2310
|
-
resource_group_name
|
|
2311
|
-
tenant_id
|
|
2312
|
-
sku_name
|
|
2397
|
+
name = "${names.keyVault}"
|
|
2398
|
+
location = azurerm_resource_group.main.location
|
|
2399
|
+
resource_group_name = azurerm_resource_group.main.name
|
|
2400
|
+
tenant_id = data.azurerm_client_config.current.tenant_id
|
|
2401
|
+
sku_name = "standard"
|
|
2402
|
+
rbac_authorization_enabled = true
|
|
2313
2403
|
}
|
|
2314
2404
|
|
|
2315
2405
|
data "azurerm_client_config" "current" {}
|
|
@@ -2383,17 +2473,18 @@ Build the generated backend in ACR, then replace the bootstrap image:
|
|
|
2383
2473
|
|
|
2384
2474
|
\`\`\`bash
|
|
2385
2475
|
ACR_NAME="$(tofu output -raw container_registry_name)"
|
|
2386
|
-
|
|
2476
|
+
SOURCE_SHA="$(git rev-parse HEAD)"
|
|
2477
|
+
az acr build --registry "$ACR_NAME" --image ${plan.safeProjectName}-backend:"$SOURCE_SHA" ../../..
|
|
2387
2478
|
${plan.includeFrontend ? `BACKEND_URL="https://$(tofu output -raw backend_url)"
|
|
2388
|
-
az acr build --registry "$ACR_NAME" --image ${plan.safeProjectName}-frontend:
|
|
2479
|
+
az acr build --registry "$ACR_NAME" --image ${plan.safeProjectName}-frontend:"$SOURCE_SHA" --build-arg VITE_API_BASE_URL="$BACKEND_URL" ../../../frontend
|
|
2389
2480
|
` : ''}\`\`\`
|
|
2390
2481
|
|
|
2391
2482
|
Persist the deployed images in \`environments/${env}.tfvars\` so future applies do not restore the bootstrap image:
|
|
2392
2483
|
|
|
2393
2484
|
\`\`\`hcl
|
|
2394
|
-
backend_image = "<login-server>/${plan.safeProjectName}-backend
|
|
2485
|
+
backend_image = "<login-server>/${plan.safeProjectName}-backend@sha256:<manifest-digest>"
|
|
2395
2486
|
backend_target_port = 8000
|
|
2396
|
-
${plan.includeFrontend ? `frontend_image = "<login-server>/${plan.safeProjectName}-frontend
|
|
2487
|
+
${plan.includeFrontend ? `frontend_image = "<login-server>/${plan.safeProjectName}-frontend@sha256:<manifest-digest>"
|
|
2397
2488
|
` : ''}\`\`\`
|
|
2398
2489
|
|
|
2399
2490
|
\`\`\`bash
|
|
@@ -2414,15 +2505,15 @@ function renderTofuTfvars(plan, environment) {
|
|
|
2414
2505
|
['environment', JSON.stringify(environment)],
|
|
2415
2506
|
['location', JSON.stringify(plan.region.slug)],
|
|
2416
2507
|
['resource_suffix', JSON.stringify(stableResourceSuffix(plan, environment))],
|
|
2417
|
-
['backend_image', JSON.stringify('
|
|
2508
|
+
['backend_image', JSON.stringify(formatContainerImage(supportedStack.containers['container-apps-bootstrap']))],
|
|
2418
2509
|
['backend_target_port', '80'],
|
|
2419
2510
|
['enable_private_networking', 'false']
|
|
2420
2511
|
];
|
|
2421
2512
|
if (plan.includeFrontend) {
|
|
2422
|
-
values.push(['frontend_image', JSON.stringify('
|
|
2513
|
+
values.push(['frontend_image', JSON.stringify(formatContainerImage(supportedStack.containers['container-apps-bootstrap']))]);
|
|
2423
2514
|
}
|
|
2424
2515
|
if (hasFunctionWorker(plan)) {
|
|
2425
|
-
values.push(['function_worker_queue_name', JSON.stringify(DEFAULT_FUNCTION_WORKER_QUEUE_NAME)], ['functions_python_version', JSON.stringify(
|
|
2516
|
+
values.push(['function_worker_queue_name', JSON.stringify(DEFAULT_FUNCTION_WORKER_QUEUE_NAME)], ['functions_python_version', JSON.stringify(supportedStack.runtimes.python.releaseLine)]);
|
|
2426
2517
|
}
|
|
2427
2518
|
const width = Math.max(...values.map(([key]) => key.length));
|
|
2428
2519
|
return values.map(([key, value]) => `${key.padEnd(width)} = ${value}`).join('\n');
|
|
@@ -2804,16 +2895,15 @@ async function submit(): Promise<void> {
|
|
|
2804
2895
|
`;
|
|
2805
2896
|
}
|
|
2806
2897
|
function renderFrontendStyles() {
|
|
2807
|
-
return `@
|
|
2808
|
-
@tailwind components;
|
|
2809
|
-
@tailwind utilities;
|
|
2898
|
+
return `@import "tailwindcss";
|
|
2810
2899
|
`;
|
|
2811
2900
|
}
|
|
2812
2901
|
function renderFrontendViteConfig() {
|
|
2813
2902
|
return `import { defineConfig } from 'vite';
|
|
2814
|
-
import
|
|
2903
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
2904
|
+
import vue from '@vitejs/plugin-vue';
|
|
2815
2905
|
|
|
2816
|
-
export default defineConfig({ plugins: [vue()] });
|
|
2906
|
+
export default defineConfig({ plugins: [vue(), tailwindcss()] });
|
|
2817
2907
|
`;
|
|
2818
2908
|
}
|
|
2819
2909
|
function renderFrontendTailwindConfig() {
|
|
@@ -2827,16 +2917,17 @@ export default {
|
|
|
2827
2917
|
`;
|
|
2828
2918
|
}
|
|
2829
2919
|
function renderFrontendDockerfile() {
|
|
2830
|
-
return `FROM node
|
|
2920
|
+
return `FROM ${formatContainerImage(supportedStack.containers['node-runtime'])} AS build
|
|
2831
2921
|
WORKDIR /app
|
|
2922
|
+
ARG NPM_CONFIG_REGISTRY=https://registry.npmjs.org
|
|
2832
2923
|
COPY package.json package-lock.json* ./
|
|
2833
|
-
RUN npm
|
|
2924
|
+
RUN npm ci --ignore-scripts --no-audit --no-fund --registry="$NPM_CONFIG_REGISTRY"
|
|
2834
2925
|
COPY . .
|
|
2835
2926
|
ARG VITE_API_BASE_URL=http://localhost:8000
|
|
2836
2927
|
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
|
|
2837
2928
|
RUN npm run build
|
|
2838
2929
|
|
|
2839
|
-
FROM nginx
|
|
2930
|
+
FROM ${formatContainerImage(supportedStack.containers['nginx-runtime'])}
|
|
2840
2931
|
COPY --from=build /app/dist /usr/share/nginx/html
|
|
2841
2932
|
`;
|
|
2842
2933
|
}
|