@tdk-landscape/tdk-cli-core 1.3.53 → 1.3.54
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/CHANGELOG.md +4 -0
- package/cli/dist/generator/extension-fetch.js +16 -9
- package/cli/package.json +1 -1
- package/package.json +16 -1
- package/.claude/skills/layer-autoresearch/SKILL.md +0 -48
- package/.claude/skills/tdk-troubleshoot/SKILL.md +0 -222
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -40
- package/.github/ISSUE_TEMPLATE/config.yml +0 -11
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -18
- package/.github/ISSUE_TEMPLATE/premium_license.yml +0 -43
- package/.github/dependabot.yml +0 -33
- package/.github/pull_request_template.md +0 -5
- package/.github/workflows/ci.yml +0 -200
- package/.github/workflows/release-binaries.yml +0 -139
- package/.github/zizmor.yml +0 -9
- package/.pre-commit-config.yaml +0 -57
- package/CODE_OF_CONDUCT.md +0 -9
- package/CONTRIBUTING.md +0 -42
- package/Makefile +0 -92
- package/SECURITY.md +0 -17
- package/benchmarks/results/container-scale-2026-09-25T20-54-49-736Z.json +0 -45
- package/benchmarks/results/container-scale-2026-09-25T21-01-20-540Z.json +0 -150
- package/benchmarks/results/container-scale-2026-09-25T21-15-01-441Z.json +0 -73
- package/benchmarks/results/container-scale-2026-09-25T22-09-01-630Z.json +0 -77
- package/benchmarks/results/container-scale-2026-09-25T22-18-28-371Z.json +0 -202
- package/cli/AGENTS.md +0 -390
- package/cli/DESIGN.md +0 -735
- package/cli/README.md +0 -292
- package/cli/biome.json +0 -40
- package/cli/dependency-graph.svg +0 -1135
- package/cli/dist/cli.d.ts +0 -3
- package/cli/dist/cli.d.ts.map +0 -1
- package/cli/dist/cli.js.map +0 -1
- package/cli/dist/commands/completion.d.ts +0 -3
- package/cli/dist/commands/completion.d.ts.map +0 -1
- package/cli/dist/commands/completion.js.map +0 -1
- package/cli/dist/commands/config.d.ts +0 -3
- package/cli/dist/commands/config.d.ts.map +0 -1
- package/cli/dist/commands/config.js.map +0 -1
- package/cli/dist/commands/doctor.d.ts +0 -19
- package/cli/dist/commands/doctor.d.ts.map +0 -1
- package/cli/dist/commands/doctor.js.map +0 -1
- package/cli/dist/commands/down.d.ts +0 -3
- package/cli/dist/commands/down.d.ts.map +0 -1
- package/cli/dist/commands/down.js.map +0 -1
- package/cli/dist/commands/help.d.ts +0 -2
- package/cli/dist/commands/help.d.ts.map +0 -1
- package/cli/dist/commands/help.js.map +0 -1
- package/cli/dist/commands/networks.d.ts +0 -3
- package/cli/dist/commands/networks.d.ts.map +0 -1
- package/cli/dist/commands/networks.js.map +0 -1
- package/cli/dist/commands/project.d.ts +0 -9
- package/cli/dist/commands/project.d.ts.map +0 -1
- package/cli/dist/commands/project.js.map +0 -1
- package/cli/dist/commands/projects.d.ts +0 -3
- package/cli/dist/commands/projects.d.ts.map +0 -1
- package/cli/dist/commands/projects.js.map +0 -1
- package/cli/dist/commands/resource.d.ts +0 -69
- package/cli/dist/commands/resource.d.ts.map +0 -1
- package/cli/dist/commands/resource.js.map +0 -1
- package/cli/dist/commands/resources.d.ts +0 -3
- package/cli/dist/commands/resources.d.ts.map +0 -1
- package/cli/dist/commands/resources.js.map +0 -1
- package/cli/dist/commands/stack.d.ts +0 -3
- package/cli/dist/commands/stack.d.ts.map +0 -1
- package/cli/dist/commands/stack.js.map +0 -1
- package/cli/dist/commands/stacks.d.ts +0 -3
- package/cli/dist/commands/stacks.d.ts.map +0 -1
- package/cli/dist/commands/stacks.js.map +0 -1
- package/cli/dist/commands/status.d.ts +0 -3
- package/cli/dist/commands/status.d.ts.map +0 -1
- package/cli/dist/commands/status.js.map +0 -1
- package/cli/dist/commands/ui.d.ts +0 -3
- package/cli/dist/commands/ui.d.ts.map +0 -1
- package/cli/dist/commands/ui.js.map +0 -1
- package/cli/dist/commands/up.d.ts +0 -3
- package/cli/dist/commands/up.d.ts.map +0 -1
- package/cli/dist/commands/up.js.map +0 -1
- package/cli/dist/commands/upgrade.d.ts +0 -14
- package/cli/dist/commands/upgrade.d.ts.map +0 -1
- package/cli/dist/commands/upgrade.js.map +0 -1
- package/cli/dist/commands/version.d.ts +0 -3
- package/cli/dist/commands/version.d.ts.map +0 -1
- package/cli/dist/commands/version.js.map +0 -1
- package/cli/dist/components/Accessible.d.ts +0 -8
- package/cli/dist/components/Accessible.d.ts.map +0 -1
- package/cli/dist/components/Accessible.js.map +0 -1
- package/cli/dist/components/BaseTooltip.d.ts +0 -4
- package/cli/dist/components/BaseTooltip.d.ts.map +0 -1
- package/cli/dist/components/BaseTooltip.js.map +0 -1
- package/cli/dist/components/DetailPanel.d.ts +0 -4
- package/cli/dist/components/DetailPanel.d.ts.map +0 -1
- package/cli/dist/components/DetailPanel.js.map +0 -1
- package/cli/dist/components/FileTree.d.ts +0 -4
- package/cli/dist/components/FileTree.d.ts.map +0 -1
- package/cli/dist/components/FileTree.js.map +0 -1
- package/cli/dist/components/ResourceSelectInput.d.ts +0 -4
- package/cli/dist/components/ResourceSelectInput.d.ts.map +0 -1
- package/cli/dist/components/ResourceSelectInput.js.map +0 -1
- package/cli/dist/components/ResourceTable.d.ts +0 -4
- package/cli/dist/components/ResourceTable.d.ts.map +0 -1
- package/cli/dist/components/ResourceTable.js.map +0 -1
- package/cli/dist/components/TabBar.d.ts +0 -4
- package/cli/dist/components/TabBar.d.ts.map +0 -1
- package/cli/dist/components/TabBar.js.map +0 -1
- package/cli/dist/components/Tooltip.d.ts +0 -12
- package/cli/dist/components/Tooltip.d.ts.map +0 -1
- package/cli/dist/components/Tooltip.js.map +0 -1
- package/cli/dist/components/index.d.ts +0 -8
- package/cli/dist/components/index.d.ts.map +0 -1
- package/cli/dist/components/index.js.map +0 -1
- package/cli/dist/config/platform-standards.d.ts +0 -159
- package/cli/dist/config/platform-standards.d.ts.map +0 -1
- package/cli/dist/config/platform-standards.js.map +0 -1
- package/cli/dist/generator/extension-fetch.d.ts +0 -25
- package/cli/dist/generator/extension-fetch.d.ts.map +0 -1
- package/cli/dist/generator/extension-fetch.js.map +0 -1
- package/cli/dist/generator/template-engine.d.ts +0 -62
- package/cli/dist/generator/template-engine.d.ts.map +0 -1
- package/cli/dist/generator/template-engine.js.map +0 -1
- package/cli/dist/index.d.ts +0 -11
- package/cli/dist/index.d.ts.map +0 -1
- package/cli/dist/index.js.map +0 -1
- package/cli/dist/types/index.d.ts +0 -257
- package/cli/dist/types/index.d.ts.map +0 -1
- package/cli/dist/types/index.js.map +0 -1
- package/cli/dist/utils/cache.d.ts +0 -19
- package/cli/dist/utils/cache.d.ts.map +0 -1
- package/cli/dist/utils/cache.js.map +0 -1
- package/cli/dist/utils/command-helpers.d.ts +0 -17
- package/cli/dist/utils/command-helpers.d.ts.map +0 -1
- package/cli/dist/utils/command-helpers.js.map +0 -1
- package/cli/dist/utils/constants.d.ts +0 -53
- package/cli/dist/utils/constants.d.ts.map +0 -1
- package/cli/dist/utils/constants.js.map +0 -1
- package/cli/dist/utils/discovery-context.d.ts +0 -4
- package/cli/dist/utils/discovery-context.d.ts.map +0 -1
- package/cli/dist/utils/discovery-context.js.map +0 -1
- package/cli/dist/utils/docker.d.ts +0 -4
- package/cli/dist/utils/docker.d.ts.map +0 -1
- package/cli/dist/utils/docker.js.map +0 -1
- package/cli/dist/utils/doctor-runtime.d.ts +0 -81
- package/cli/dist/utils/doctor-runtime.d.ts.map +0 -1
- package/cli/dist/utils/doctor-runtime.js.map +0 -1
- package/cli/dist/utils/env-validator.d.ts +0 -15
- package/cli/dist/utils/env-validator.d.ts.map +0 -1
- package/cli/dist/utils/env-validator.js.map +0 -1
- package/cli/dist/utils/errors.d.ts +0 -31
- package/cli/dist/utils/errors.d.ts.map +0 -1
- package/cli/dist/utils/errors.js.map +0 -1
- package/cli/dist/utils/file-helpers.d.ts +0 -14
- package/cli/dist/utils/file-helpers.d.ts.map +0 -1
- package/cli/dist/utils/file-helpers.js.map +0 -1
- package/cli/dist/utils/formatting.d.ts +0 -30
- package/cli/dist/utils/formatting.d.ts.map +0 -1
- package/cli/dist/utils/formatting.js.map +0 -1
- package/cli/dist/utils/paths.d.ts +0 -3
- package/cli/dist/utils/paths.d.ts.map +0 -1
- package/cli/dist/utils/paths.js.map +0 -1
- package/cli/dist/utils/port-assignment.d.ts +0 -6
- package/cli/dist/utils/port-assignment.d.ts.map +0 -1
- package/cli/dist/utils/port-assignment.js.map +0 -1
- package/cli/dist/utils/project-features.d.ts +0 -23
- package/cli/dist/utils/project-features.d.ts.map +0 -1
- package/cli/dist/utils/project-features.js.map +0 -1
- package/cli/dist/utils/project-templates.d.ts +0 -7
- package/cli/dist/utils/project-templates.d.ts.map +0 -1
- package/cli/dist/utils/project-templates.js.map +0 -1
- package/cli/dist/utils/prompt.d.ts +0 -28
- package/cli/dist/utils/prompt.d.ts.map +0 -1
- package/cli/dist/utils/prompt.js.map +0 -1
- package/cli/dist/utils/resource-features.d.ts +0 -29
- package/cli/dist/utils/resource-features.d.ts.map +0 -1
- package/cli/dist/utils/resource-features.js.map +0 -1
- package/cli/dist/utils/service-urls.d.ts +0 -30
- package/cli/dist/utils/service-urls.d.ts.map +0 -1
- package/cli/dist/utils/service-urls.js.map +0 -1
- package/cli/dist/utils/services.d.ts +0 -18
- package/cli/dist/utils/services.d.ts.map +0 -1
- package/cli/dist/utils/services.js.map +0 -1
- package/cli/dist/utils/stack-features.d.ts +0 -22
- package/cli/dist/utils/stack-features.d.ts.map +0 -1
- package/cli/dist/utils/stack-features.js.map +0 -1
- package/cli/dist/utils/tar.d.ts +0 -7
- package/cli/dist/utils/tar.d.ts.map +0 -1
- package/cli/dist/utils/tar.js.map +0 -1
- package/cli/dist/utils/tilt.d.ts +0 -20
- package/cli/dist/utils/tilt.d.ts.map +0 -1
- package/cli/dist/utils/tilt.js.map +0 -1
- package/cli/dist/utils/validation.d.ts +0 -16
- package/cli/dist/utils/validation.d.ts.map +0 -1
- package/cli/dist/utils/validation.js.map +0 -1
- package/cli/knip.json +0 -6
- package/cli/pnpm-lock.yaml +0 -2305
- package/cli/pnpm-workspace.yaml +0 -2
- package/cli/src/cli.ts +0 -72
- package/cli/src/commands/__tests__/always-enabled-infra.test.ts +0 -109
- package/cli/src/commands/__tests__/config.test.ts +0 -146
- package/cli/src/commands/__tests__/doctor.test.ts +0 -730
- package/cli/src/commands/__tests__/error-handling.test.ts +0 -78
- package/cli/src/commands/__tests__/phases-model.test.ts +0 -161
- package/cli/src/commands/__tests__/project-resource-features-e2e.test.ts +0 -187
- package/cli/src/commands/__tests__/project.test.ts +0 -68
- package/cli/src/commands/__tests__/resource.test.ts +0 -226
- package/cli/src/commands/__tests__/saas-starter-integration.test.ts +0 -194
- package/cli/src/commands/__tests__/stack-features-e2e.test.ts +0 -135
- package/cli/src/commands/__tests__/upgrade.test.ts +0 -198
- package/cli/src/commands/completion.ts +0 -300
- package/cli/src/commands/config.ts +0 -299
- package/cli/src/commands/doctor.ts +0 -926
- package/cli/src/commands/down.ts +0 -37
- package/cli/src/commands/help.ts +0 -143
- package/cli/src/commands/networks-design.md +0 -80
- package/cli/src/commands/networks.ts +0 -332
- package/cli/src/commands/project.ts +0 -456
- package/cli/src/commands/projects.ts +0 -82
- package/cli/src/commands/resource.ts +0 -652
- package/cli/src/commands/resources.ts +0 -95
- package/cli/src/commands/stack.ts +0 -128
- package/cli/src/commands/stacks.ts +0 -52
- package/cli/src/commands/status.ts +0 -88
- package/cli/src/commands/ui.tsx +0 -843
- package/cli/src/commands/up.ts +0 -169
- package/cli/src/commands/upgrade.ts +0 -628
- package/cli/src/commands/version.ts +0 -9
- package/cli/src/components/Accessible.tsx +0 -21
- package/cli/src/components/BaseTooltip.tsx +0 -66
- package/cli/src/components/DetailPanel.tsx +0 -146
- package/cli/src/components/FileTree.tsx +0 -89
- package/cli/src/components/ResourceSelectInput.tsx +0 -30
- package/cli/src/components/ResourceTable.tsx +0 -78
- package/cli/src/components/TabBar.tsx +0 -53
- package/cli/src/components/Tooltip.tsx +0 -11
- package/cli/src/components/index.ts +0 -7
- package/cli/src/config/platform-standards.test.ts +0 -11
- package/cli/src/config/platform-standards.ts +0 -196
- package/cli/src/generator/__tests__/extension-fetch.test.ts +0 -242
- package/cli/src/generator/__tests__/template-engine.test.ts +0 -70
- package/cli/src/generator/extension-fetch.ts +0 -286
- package/cli/src/generator/template-engine.ts +0 -752
- package/cli/src/index.ts +0 -86
- package/cli/src/templates.d.ts +0 -4
- package/cli/src/types/index.ts +0 -353
- package/cli/src/utils/__tests__/cache.test.ts +0 -98
- package/cli/src/utils/__tests__/formatting.test.ts +0 -170
- package/cli/src/utils/__tests__/paths.test.ts +0 -180
- package/cli/src/utils/__tests__/port-assignment.test.ts +0 -121
- package/cli/src/utils/__tests__/service-urls.test.ts +0 -174
- package/cli/src/utils/__tests__/services.test.ts +0 -531
- package/cli/src/utils/__tests__/tilt.test.ts +0 -172
- package/cli/src/utils/__tests__/validation.test.ts +0 -148
- package/cli/src/utils/cache.ts +0 -76
- package/cli/src/utils/command-helpers.ts +0 -54
- package/cli/src/utils/constants.ts +0 -130
- package/cli/src/utils/discovery-context.ts +0 -49
- package/cli/src/utils/docker.ts +0 -20
- package/cli/src/utils/doctor-runtime.ts +0 -644
- package/cli/src/utils/env-validator.ts +0 -139
- package/cli/src/utils/errors.ts +0 -168
- package/cli/src/utils/file-helpers.ts +0 -62
- package/cli/src/utils/formatting.ts +0 -215
- package/cli/src/utils/paths.ts +0 -53
- package/cli/src/utils/port-assignment.ts +0 -106
- package/cli/src/utils/project-features.ts +0 -118
- package/cli/src/utils/project-templates.ts +0 -29
- package/cli/src/utils/prompt.ts +0 -128
- package/cli/src/utils/resource-features.ts +0 -215
- package/cli/src/utils/service-urls.ts +0 -124
- package/cli/src/utils/services.ts +0 -364
- package/cli/src/utils/stack-features.ts +0 -30
- package/cli/src/utils/tar.ts +0 -24
- package/cli/src/utils/tilt.ts +0 -133
- package/cli/src/utils/validation.ts +0 -67
- package/cli/tsconfig.json +0 -25
- package/cli/tsconfig.typecheck.json +0 -8
- package/docs/FEATURES.md +0 -281
- package/docs/demo.svg +0 -217
- package/engine/docs/AUTODISCOVERY_TEST.md +0 -182
- package/engine/docs/C4-ARCHITECTURE-operations.md +0 -396
- package/engine/docs/C4-ARCHITECTURE-overview.md +0 -284
- package/engine/docs/C4-ARCHITECTURE.md +0 -17
- package/engine/docs/GOLDEN_IMAGE_INTEGRATION.md +0 -349
- package/engine/docs/MANIFEST_DRIVEN_ARCHITECTURE.md +0 -357
- package/engine/docs/README.md +0 -284
- package/install.sh +0 -19
- package/knip.json +0 -10
- package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/.openspec.yaml +0 -2
- package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/design.md +0 -102
- package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/proposal.md +0 -31
- package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-notifications/spec.md +0 -48
- package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-orchestration/spec.md +0 -63
- package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/specs/vscode-tdk-troubleshooting/spec.md +0 -56
- package/openspec/changes/add-vscode-tdk-troubleshooter-plugin/tasks.md +0 -61
- package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/design.md +0 -114
- package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/proposal.md +0 -32
- package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/manifest-field-migration/spec.md +0 -20
- package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/resource-terminology-standard/spec.md +0 -20
- package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/specs/stack-terminology-standard/spec.md +0 -20
- package/openspec/changes/archive/2026-04-28-rename-domain-to-stack-service-to-resource/tasks.md +0 -194
- package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/design.md +0 -111
- package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/proposal.md +0 -33
- package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/function-naming-alignment/spec.md +0 -12
- package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/optional-parameter-patterns/spec.md +0 -18
- package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-documentation/spec.md +0 -16
- package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-naming-consistency/spec.md +0 -12
- package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/specs/parameter-ordering-standard/spec.md +0 -12
- package/openspec/changes/archive/2026-04-28-star-files-parameter-fixes/tasks.md +0 -194
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/design.md +0 -84
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/proposal.md +0 -31
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/codebase-audit/spec.md +0 -27
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/dead-code-cleanup/spec.md +0 -27
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/test-gap-fill/spec.md +0 -23
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/specs/todo-resolution/spec.md +0 -27
- package/openspec/changes/archive/2026-04-29-scan-repo-gaps-fix/tasks.md +0 -39
- package/openspec/changes/archive/2026-06-25-landscape-consolidation/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-25-landscape-consolidation/design.md +0 -80
- package/openspec/changes/archive/2026-06-25-landscape-consolidation/proposal.md +0 -40
- package/openspec/changes/archive/2026-06-25-landscape-consolidation/spec.md +0 -52
- package/openspec/changes/archive/2026-06-25-landscape-consolidation/tasks.md +0 -51
- package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/design.md +0 -43
- package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/proposal.md +0 -29
- package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/specs/self-contained-generated-projects/spec.md +0 -67
- package/openspec/changes/archive/2026-09-17-fix-generated-stack-boot/tasks.md +0 -27
- package/openspec/changes/optimize-runtime-docker-layers/.openspec.yaml +0 -3
- package/openspec/changes/optimize-runtime-docker-layers/design.md +0 -81
- package/openspec/changes/optimize-runtime-docker-layers/proposal.md +0 -44
- package/openspec/changes/optimize-runtime-docker-layers/specs/runtime-container-footprint/spec.md +0 -79
- package/openspec/changes/optimize-runtime-docker-layers/tasks.md +0 -36
- package/openspec/changes/prioritized-cold-start/.openspec.yaml +0 -4
- package/openspec/changes/prioritized-cold-start/design.md +0 -137
- package/openspec/changes/prioritized-cold-start/proposal.md +0 -41
- package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +0 -98
- package/openspec/changes/prioritized-cold-start/tasks.md +0 -69
- package/openspec/config.yaml +0 -1
- package/openspec/specs/manifest-field-migration/spec.md +0 -24
- package/openspec/specs/resource-terminology-standard/spec.md +0 -24
- package/openspec/specs/self-contained-generated-projects/spec.md +0 -67
- package/openspec/specs/stack-terminology-standard/spec.md +0 -24
- package/platform/services/platform/proxy/service.yaml +0 -14
- package/scripts/audit-codebase.sh +0 -213
- package/scripts/autoresearch/layer-eval.sh +0 -65
- package/scripts/benchmark/README.md +0 -25
- package/scripts/benchmark/container-scale.ts +0 -503
- package/scripts/benchmark/pre-release-gate.sh +0 -9
- package/scripts/install-git-hooks.sh +0 -14
- package/scripts/measure-idle-footprint.sh +0 -58
- package/scripts/record-demo.sh +0 -43
- package/scripts/release-binaries.sh +0 -97
- package/scripts/setup-pre-commit.sh +0 -33
- package/scripts/stage-npm-engine.sh +0 -36
- package/tests/tilt-engine/__init__.py +0 -5
- package/tests/tilt-engine/conftest.py +0 -182
- package/tests/tilt-engine/fixtures/appointment-frontend-service.json +0 -11
- package/tests/tilt-engine/fixtures/identity-backend-service.json +0 -12
- package/tests/tilt-engine/fixtures/invalid-service.json +0 -4
- package/tests/tilt-engine/test_agents_md_generator.py +0 -341
- package/tests/tilt-engine/test_agents_md_integration.py +0 -378
- package/tests/tilt-engine/test_cache_operations.py +0 -181
- package/tests/tilt-engine/test_cache_persistence.py +0 -142
- package/tests/tilt-engine/test_code_executor.py +0 -152
- package/tests/tilt-engine/test_code_viewer.py +0 -127
- package/tests/tilt-engine/test_compose_port_anchors.py +0 -310
- package/tests/tilt-engine/test_config_inspector.py +0 -162
- package/tests/tilt-engine/test_daemon_focus_mode.py +0 -89
- package/tests/tilt-engine/test_daemon_polling.py +0 -122
- package/tests/tilt-engine/test_daemon_registration.py +0 -115
- package/tests/tilt-engine/test_daemon_validation.py +0 -107
- package/tests/tilt-engine/test_docker_generator_bugs.py +0 -493
- package/tests/tilt-engine/test_docker_prune.py +0 -119
- package/tests/tilt-engine/test_dockerignore_path_bug.py +0 -82
- package/tests/tilt-engine/test_file_browser.py +0 -193
- package/tests/tilt-engine/test_frontend_basepath_manifest.py +0 -27
- package/tests/tilt-engine/test_health_dependencies.py +0 -130
- package/tests/tilt-engine/test_health_endpoints.py +0 -118
- package/tests/tilt-engine/test_health_tilt_ui.py +0 -85
- package/tests/tilt-engine/test_runtime_container_footprint.py +0 -162
- package/tests/tilt-engine/test_sablier_defer_start.py +0 -237
- package/tests/tilt-engine/test_safety_aliases.py +0 -84
- package/tests/tilt-engine/test_snapshot_diff.py +0 -136
- package/tests/tilt-engine/test_snapshot_persistence.py +0 -163
- package/tests/tilt-engine/test_snapshot_scanning.py +0 -123
- package/tests/tilt-engine/test_standalone_project_boot.py +0 -61
- package/tests/tilt-engine/test_standalone_traefik.py +0 -160
- package/tests/tilt-engine/test_traefik_static_wake_route.py +0 -187
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# Self-Contained Generated Projects
|
|
2
|
-
|
|
3
|
-
## ADDED Requirements
|
|
4
|
-
|
|
5
|
-
### Requirement: Generated Services Compile On First Run
|
|
6
|
-
|
|
7
|
-
Generated backend services must always emit valid Bun source code.
|
|
8
|
-
|
|
9
|
-
#### Scenario: Backend template with banner logs
|
|
10
|
-
|
|
11
|
-
- **WHEN** a user runs `tdk resource api --type backend --stack pre-alpha --path services/pre-alpha/api`
|
|
12
|
-
- **THEN** the generated `src/index.ts` contains `console.log('\n🚀 api running ...')` with an escaped `\n`
|
|
13
|
-
- **AND** `bun run src/index.ts` starts the server without a syntax error
|
|
14
|
-
|
|
15
|
-
#### Scenario: Health endpoint serves
|
|
16
|
-
|
|
17
|
-
- **WHEN** the generated service is running
|
|
18
|
-
- **THEN** `GET /health` responds `{"status":"ok","service":"<name>"}`
|
|
19
|
-
|
|
20
|
-
### Requirement: Extension Vendored Into Generated Projects
|
|
21
|
-
|
|
22
|
-
Every generated project must contain the TDK Tilt extension so `tdk up` works offline.
|
|
23
|
-
|
|
24
|
-
#### Scenario: Project generation
|
|
25
|
-
|
|
26
|
-
- **WHEN** a user runs `tdk project --yes`
|
|
27
|
-
- **THEN** `.tdk/.tdk-out/tdk-cli-ext/` is created containing `Tiltfile`, `engine/`, `discovery/`, `specs/`, and `ext/`
|
|
28
|
-
- **AND** the generation log reports `Vendored TDK extension -> .tdk/.tdk-out/tdk-cli-ext/`
|
|
29
|
-
|
|
30
|
-
#### Scenario: Extension source auto-detection
|
|
31
|
-
|
|
32
|
-
- **WHEN** generating a project
|
|
33
|
-
- **THEN** the extension source is resolved in order: `$TDK_EXTENSION_SOURCE`, an executable-adjacent `tdk-cli` checkout, then a `../tdk-cli` sibling of the project root
|
|
34
|
-
|
|
35
|
-
### Requirement: Tiltfile Loads Extension Locally
|
|
36
|
-
|
|
37
|
-
The generated Tiltfile must resolve the extension without any GitHub dependency.
|
|
38
|
-
|
|
39
|
-
#### Scenario: Vendored copy present
|
|
40
|
-
|
|
41
|
-
- **WHEN** `.tdk/.tdk-out/tdk-cli-ext/` exists next to the Tiltfile
|
|
42
|
-
- **THEN** the Tiltfile registers `file://<vendored path>` as the extension repo
|
|
43
|
-
- **AND** `load('ext://tdk-cli', ...)` succeeds without cloning
|
|
44
|
-
|
|
45
|
-
#### Scenario: Explicit extension path override
|
|
46
|
-
|
|
47
|
-
- **WHEN** the vendored copy is absent but `$TDK_EXTENSION_PATH` points at a valid tdk-cli checkout
|
|
48
|
-
- **THEN** the Tiltfile uses that path
|
|
49
|
-
|
|
50
|
-
#### Scenario: No extension available
|
|
51
|
-
|
|
52
|
-
- **WHEN** neither the vendored copy nor `$TDK_EXTENSION_PATH` exists
|
|
53
|
-
- **THEN** loading fails fast with a single actionable error listing the three recovery options, with no retry loop
|
|
54
|
-
|
|
55
|
-
### Requirement: No Machine-Specific Paths In Generated Output
|
|
56
|
-
|
|
57
|
-
Generated Tiltfiles must never contain hardcoded author or machine paths.
|
|
58
|
-
|
|
59
|
-
#### Scenario: Fresh generation on any machine
|
|
60
|
-
|
|
61
|
-
- **WHEN** any user generates a project
|
|
62
|
-
- **THEN** the generated `Tiltfile` has no absolute host-specific paths and no private GitHub repo URL fallback
|
|
63
|
-
|
|
64
|
-
#### Scenario: Local probe is truthful
|
|
65
|
-
|
|
66
|
-
- **WHEN** the Tiltfile probes for the vendored directory
|
|
67
|
-
- **THEN** the probe echoes its outcome (`test -d <path> && echo yes || echo no`) so detection reflects the filesystem
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
## 1. Compilable Generated Backend Source
|
|
2
|
-
|
|
3
|
-
- [x] 1.1 Update `getBackendIndexTemplate` in `cli/src/commands/resource.ts` so the emitted banner lines use `\\n` (escaped backslash-n): `console.log('\n...')` becomes two characters in generated files
|
|
4
|
-
- [x] 1.2 Generate a backend service with the CLI and assert `src/index.ts` has no raw newline inside the `console.log` string
|
|
5
|
-
- [x] 1.3 Boot the generated service with `bun run src/index.ts` and assert `GET /health` returns `{"status":"ok","service":"<name>"}`
|
|
6
|
-
|
|
7
|
-
## 2. Extension Vendored Into Generated Projects
|
|
8
|
-
|
|
9
|
-
- [x] 2.1 Add `vendorTdkExtension(projectRoot)` to `cli/src/generator/template-engine.ts`, called from `generateMasterConfigs`, copying `Tiltfile`, `engine/`, `discovery/`, `specs/`, `ext/` into `.tdk/.tdk-out/tdk-cli-ext/`
|
|
10
|
-
- [x] 2.2 Resolve the extension source in order: `$TDK_EXTENSION_SOURCE` -> executable-adjacent `tdk-cli` checkout -> `../tdk-cli` sibling of the project root
|
|
11
|
-
- [x] 2.3 Print a warning (no hard failure) when no extension source is found
|
|
12
|
-
- [x] 2.4 Assert generation reports `Vendored TDK extension -> .tdk/.tdk-out/tdk-cli-ext/` and the directory exists
|
|
13
|
-
|
|
14
|
-
## 3. Tiltfile Extension Resolution
|
|
15
|
-
|
|
16
|
-
- [x] 3.1 Rewrite `cli/templates/Tiltfile.hbs`: resolve vendored path -> `$TDK_EXTENSION_PATH` -> `fail(...)` with the three recovery options; remove the hardcoded `/private/var/www/2025/ollamar1/tdk-cli` path and the private GitHub fallback
|
|
17
|
-
- [x] 3.2 Use truthful probes: `test -d <path> && echo yes || echo no` in both branches
|
|
18
|
-
- [x] 3.3 Assert a generated Tiltfile contains no absolute host-specific paths and no `github.com/tdk-landscape/tdk-cli` URL
|
|
19
|
-
- [x] 3.4 Run `tilt ci -f .tdk/.tdk-out/Tiltfile` on a generated project and assert the vendored extension loads, services are discovered, and the workspace reports healthy
|
|
20
|
-
- [x] 3.5 Run `tdk up pre-alpha` on a two-service project and assert no git-clone retry loop; Tiltfile loading succeeds
|
|
21
|
-
|
|
22
|
-
## 4. Release Binaries
|
|
23
|
-
|
|
24
|
-
- [x] 4.1 Rebuild binaries from fixed source: `bun build --compile --target=bun-linux-amd64 --outfile tdk-linux-amd64 cli/src/cli.ts` (repeat for `bun-linux-arm64`, `bun-darwin-amd64`, `bun-darwin-arm64`)
|
|
25
|
-
- [x] 4.2 Verify each rebuilt binary: `--version` matches, `project --yes` vendors the extension, and a generated service compiles and boots
|
|
26
|
-
- [x] 4.3 Update `release-dist-*` snapshots, `checksums.txt`, and the binaries zip
|
|
27
|
-
- [x] 4.4 Publish binaries to `tdk-landscape/tdk-cli-releases` releases with the fixed snapshots
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# Design
|
|
2
|
-
|
|
3
|
-
## Context
|
|
4
|
-
|
|
5
|
-
See `proposal.md` (Why) for the measurements. Current runtime layer behaviour relevant to the approach:
|
|
6
|
-
|
|
7
|
-
- The backend runtime Dockerfile is `ENTRYPOINT ["/entrypoint.sh"]` + `CMD ["bun", "run", "start"]`. `infisical-entrypoint.sh` ends in `exec "$@"`, so the `bun run start` wrapper becomes PID 1 and spawns the real `bun run dist/index.js` child. The wrapper is currently what receives `SIGTERM` from `docker stop` and passes it on to the child.
|
|
8
|
-
- The start command is set in several places: `RUNTIME_CONFIG.bun.backend_start_command` (`l3_builder_layers.star`), default `cmd` parameters in `dockerfile.star`, `golden_docker_generator_v2.star` and `l4_runtime_layers.star`, the `_runtime_setup` default (`infisical_docker.star`), and a hard-coded `CMD` for the Bun-static frontend (`l4_runtime_layers.star:213`). The Node runtime already uses `node dist/index.js` directly.
|
|
9
|
-
- Healthcheck timing lives in `get_docker_healthcheck_config` (`golden_image_dockerfile.star:6`), but `compose.star` (lines 141, 264) hard-codes `interval: 10s` with `retries: 6`.
|
|
10
|
-
- The per-service L4 layer already installs hugo only when a service's `featuresEnabled` contains `hugo`. The golden `l4_backend_bun` stage installs it for every backend anyway.
|
|
11
|
-
|
|
12
|
-
## Goals / Non-Goals
|
|
13
|
-
|
|
14
|
-
**Goals:**
|
|
15
|
-
- Meet every requirement in `specs/runtime-container-footprint/spec.md` without changing any service's source files or `package.json`.
|
|
16
|
-
- Keep a single source of truth for healthcheck timing.
|
|
17
|
-
|
|
18
|
-
**Non-Goals:**
|
|
19
|
-
- Reworking the entrypoint script beyond what signal handling needs.
|
|
20
|
-
- Default startup scope and Sablier scale-to-zero (see proposal Non-goals).
|
|
21
|
-
|
|
22
|
-
## Decisions
|
|
23
|
-
|
|
24
|
-
### D1: Resolve the start command at generation time, exec the entry file directly
|
|
25
|
-
|
|
26
|
-
When generating a Bun backend or Bun-static frontend runtime, the generator reads the service's `package.json` `scripts.start`. If it is exactly `bun run <file>` or `bun <file>` (one file path, no `&&`, `;`, pipes or env assignments), the runtime gets `CMD ["bun", "<file>"]`. In every other case it falls back to `CMD ["bun", "run", "start"]`. The resolver (`resolve_bun_start_command` in `l4_runtime_layers.star`) runs at the single point where the backend and Bun-static frontend `CMD` are emitted, both on the plain path and the Infisical-entrypoint path. Upstream defaults (`cmd='bun run start'` in `dockerfile.star`, `golden_docker_generator_v2.star` and `infisical_docker.star`) stay as they are and act as the "no explicit override" value. An explicit different `cmd` passed by a caller is always kept.
|
|
27
|
-
|
|
28
|
-
*Alternatives considered:*
|
|
29
|
-
- Rewriting `start` in each service's `package.json`: rejected because it edits user-owned files and breaks `bun run start` locally.
|
|
30
|
-
- Always emitting `bun <file>` from a fixed path: rejected because it silently ignores custom start scripts.
|
|
31
|
-
- `bun run --bun start`: rejected because it still keeps the wrapper process.
|
|
32
|
-
|
|
33
|
-
### D2: Add `init: true` to generated runtime services
|
|
34
|
-
|
|
35
|
-
With direct exec, Bun becomes PID 1. The kernel does not apply default signal actions to PID 1, so an app without a `SIGTERM` handler would ignore `docker stop` and be killed only after the stop timeout. Generated compose services get `init: true` (Docker's bundled tini, about 1 MB resident). Tini passes signals on and reaps zombies. It is not a Bun process, so the "exactly one Bun process" requirement still holds.
|
|
36
|
-
|
|
37
|
-
*Alternative:* inject a `SIGTERM` handler into generated service templates. Rejected because it doesn't cover existing services or custom servers.
|
|
38
|
-
|
|
39
|
-
### D3: One healthcheck config, 30 s interval with a 2 s start interval
|
|
40
|
-
|
|
41
|
-
A new shared module, `engine/topologies/platform/docker/config/healthcheck.star`, replaces the per-file inlined `get_docker_healthcheck_config` copies. The copies had drifted: `l4_runtime_layers.star`'s copy lacked keys its own code read. Defaults: `interval 30s`, `start_period 30s` (unchanged), `start_interval 2s`, `timeout 5s`, `retries 3`. Golden images, the L4 runtime layer, `compose.star` and `traefik_standalone.star` all read it. Each value can be overridden per run with `TDK_HEALTHCHECK_INTERVAL_SECONDS`, `TDK_HEALTHCHECK_START_INTERVAL_SECONDS`, `TDK_HEALTHCHECK_TIMEOUT_SECONDS`, `TDK_HEALTHCHECK_START_PERIOD_SECONDS` and `TDK_HEALTHCHECK_RETRIES`. Environment variables are used because there is no per-project healthcheck setting today, and `tdk up` does not pass the project `.env` through to Tilt.
|
|
42
|
-
|
|
43
|
-
During the start period Docker probes every 2 s, so a service is marked healthy right after it starts answering (see the spec's "Startup readiness is not delayed" scenario). After that, probes drop to every 30 s. With `retries 3`, a failing service is marked unhealthy within about 90 s, compared with about 60 s today.
|
|
44
|
-
|
|
45
|
-
*Alternative:* 30 s interval with no start interval. Rejected because first-healthy time would move from about 10 s to about 30 s per container, which slows every dependency chain in `tdk up`.
|
|
46
|
-
|
|
47
|
-
`start_interval` needs Docker Engine 25+ and Compose 2.20.2+. `tdk doctor` gets a check that fails with an upgrade hint on older versions.
|
|
48
|
-
|
|
49
|
-
### D4: Golden migrator stage declares `HEALTHCHECK NONE`
|
|
50
|
-
|
|
51
|
-
Resolved by task 1.2. Migrators are one-shot jobs: every migrator container is built from the per-service `l4_migrator_runtime` stage (`prisma_runtime.star`), which already sets `HEALTHCHECK NONE` and runs `ENTRYPOINT ["/app/migrate.sh"]` to completion. The golden `l4_migrator_golden` stage's `bunx prisma --version` healthcheck is therefore always overridden and never protects anything. It only runs if someone starts the bare golden image, and then it starts a bunx/Prisma process every interval. The golden stage now sets `HEALTHCHECK NONE` too, so both layers agree.
|
|
52
|
-
|
|
53
|
-
Evidence (2026-09-25): `grep -rn L4_generate_migrator_runtime engine/` shows a single caller (`golden_docker_generator_v2.star:57`), and that stage emits `HEALTHCHECK NONE` before `ENTRYPOINT`. No project in the workspace declares `"appType": "migrator"`.
|
|
54
|
-
|
|
55
|
-
*Alternative (original plan):* a `test -x` check on a symlinked Prisma binary. Dropped because a healthcheck on a run-to-completion container has no meaning.
|
|
56
|
-
|
|
57
|
-
### D5: Remove hugo from the golden backend stage only
|
|
58
|
-
|
|
59
|
-
Delete `RUN apk add --no-cache hugo` from `l4_backend_bun` in `golden_image_dockerfile.star`. The per-service feature-gated install stays, but it never actually ran: `manifest_resource.star` called `Docker.backend(...)` without `manifest`, so `featuresEnabled` was always empty in the L4 layer. The manifest is now passed through, which makes the per-service install work and makes removing hugo from the golden stage safe.
|
|
60
|
-
|
|
61
|
-
Evidence (task 1.1, 2026-09-25): no service in `tdk-erp-system`, `tdk-saas-starter`, `tdk-restaurant-example` or `tdk-user-management` lists `"hugo"` in `service.json`, and no source, script or config file mentions hugo (search excluded `node_modules`, `.autogenerated`, `dist` and `.tdk`). Removal affects no existing project.
|
|
62
|
-
|
|
63
|
-
### D6: Verifying the idle budget
|
|
64
|
-
|
|
65
|
-
`tests/tilt-engine/test_runtime_container_footprint.py` executes the real Starlark generators through `tilt alpha tiltfile-result` (skipped when Tilt isn't installed) rather than inspecting source text. Those tests assert the generated `CMD`, `init: true` and healthcheck values. The idle budget is checked by a smoke script that generates a template backend, runs it, waits for healthy, samples `docker stats` for 60 s, and fails if memory is over 64 MiB or CPU averages 2% or more. It's run manually and in the release checklist rather than on every CI run, because it needs Docker.
|
|
66
|
-
|
|
67
|
-
## Risks / Trade-offs
|
|
68
|
-
|
|
69
|
-
- [The start-script parser misreads a script, e.g. `bun run dist/index.js --port 4000`] → Only a single file argument qualifies. Anything with extra tokens falls back to `bun run start`, so the fallback is always the current behaviour.
|
|
70
|
-
- [Docker Engine / Compose is older than 25 / 2.20.2 and rejects `start_interval`] → New `tdk doctor` version check; the release notes state the minimum version.
|
|
71
|
-
- [Unhealthy detection is slower (about 90 s vs 60 s)] → Acceptable for local dev. `retries` stays configurable.
|
|
72
|
-
- [A project relied on hugo without declaring the feature] → The build fails loudly (`hugo: not found`). Migration note: add `"hugo"` to `featuresEnabled`. No project in this workspace is affected (see D5 evidence).
|
|
73
|
-
- [Background auto-commit tooling in this workspace pushes partial engine edits] → Make each decision's edits in one sitting and run the snapshot tests before moving on, so every intermediate push is still consistent.
|
|
74
|
-
|
|
75
|
-
## Migration Plan
|
|
76
|
-
|
|
77
|
-
1. Ship the engine changes in a TDK release.
|
|
78
|
-
2. In each project: run `tdk project --yes` to re-vendor `.tdk/.tdk-out/tdk-cli-ext/` and regenerate compose files and Dockerfiles, touch the Tiltfile, then rebuild golden layers with the `golden-layers-build` resource or a fresh `tdk up`.
|
|
79
|
-
3. Existing images keep working until they are rebuilt, and nothing needs to change inside services.
|
|
80
|
-
|
|
81
|
-
**Rollback:** revert the engine change, re-run `tdk project --yes`, and rebuild the golden layers.
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# Proposal
|
|
2
|
-
|
|
3
|
-
## Why
|
|
4
|
-
|
|
5
|
-
A full-landscape `tdk up` of `tdk-erp-system` (107 resources, 110 containers) saturated a 16 GB MacBook Air on 2026-09-25. Load average reached 30–43, only 99 MB of memory was free with 8 GB compressed, and `docker ps` took 6–13 s, which made `tdk doctor` and `tdk down` fail their own 10 s Docker check. A single idle backend is cheap (0.3–1.2% CPU, about 20 MiB in its cgroup). The overload comes from per-container overhead that the generated runtime layers multiply by 110: a redundant second Bun process, a 10 s exec-based healthcheck, and tooling nobody uses baked into the golden images. Cutting that overhead at the layer level makes large landscapes usable on the laptops TDK targets.
|
|
6
|
-
|
|
7
|
-
## What Changes
|
|
8
|
-
|
|
9
|
-
- **Start the backend with one process.** The generated L4 backend runtime (and the Bun-static frontend runtime) now start the compiled entrypoint directly, e.g. `CMD ["bun", "dist/index.js"]`, instead of `bun run start`. `bun run start` launches a Bun wrapper that then spawns a second Bun runtime (about 10 MB + 52 MB resident per container). A service whose `start` script does more than run a single file keeps `bun run start`.
|
|
10
|
-
- **Add an init process for signals.** Generated runtime services run under Docker's minimal init (`init: true`) so `docker stop` still reaches the app once the wrapper is gone.
|
|
11
|
-
- **Slow down healthchecks.** The default steady-state healthcheck interval goes from 10 s to 30 s for generated compose services and golden images. A fast start interval keeps first-healthy time where it is today. The value stays configurable through the existing healthcheck config, and compose files now read that config instead of hard-coding 10 s.
|
|
12
|
-
- **BREAKING (environment):** the fast start interval needs Docker Engine 25+ and Compose 2.20.2+. `tdk doctor` gets a check for this.
|
|
13
|
-
- **Make the migrator healthcheck cheap.** The `l4_migrator_golden` healthcheck no longer runs `bunx prisma --version`, which starts a full bunx/Prisma process on every check. It checks the locally installed Prisma binary instead.
|
|
14
|
-
- **Remove hugo from the golden backend image.** `apk add hugo` is removed from the golden `l4_backend_bun` stage (about 57 MB per backend image). The per-service L4 layer already installs hugo only when a manifest lists the `hugo` feature, and that stays as is.
|
|
15
|
-
- **Document a per-container budget.** An idle generated backend has a documented footprint target, so regressions like these are visible.
|
|
16
|
-
|
|
17
|
-
Nothing here is breaking for service code. Services that relied on hugo being present without declaring the `hugo` feature must add it to `featuresEnabled`.
|
|
18
|
-
|
|
19
|
-
## Capabilities
|
|
20
|
-
|
|
21
|
-
### New Capabilities
|
|
22
|
-
- `runtime-container-footprint`: Requirements on generated runtime images and containers: one application process per container, healthcheck cadence and cost, golden image contents limited to what every service of that type needs, and an idle footprint budget.
|
|
23
|
-
|
|
24
|
-
### Modified Capabilities
|
|
25
|
-
- (none)
|
|
26
|
-
|
|
27
|
-
## Non-goals
|
|
28
|
-
|
|
29
|
-
- Changing which resources `tdk up` starts by default, or putting backends under Sablier scale-to-zero. Even with these fixes, starting all 107 services at once stays heavy on 16 GB machines. That is a separate product decision with its own change.
|
|
30
|
-
- Changing the Bun version, base OS, or Prisma version.
|
|
31
|
-
|
|
32
|
-
## Impact
|
|
33
|
-
|
|
34
|
-
- **Engine (Starlark):**
|
|
35
|
-
- `engine/topologies/platform/docker/layers/l4_runtime_layers.star`: backend and Bun-static frontend `CMD`.
|
|
36
|
-
- `engine/topologies/platform/docker/layers/infisical/infisical_docker.star`: `_runtime_setup` default command.
|
|
37
|
-
- `engine/topologies/platform/docker/layers/l3_builder_layers.star`: `RUNTIME_CONFIG.bun.backend_start_command`.
|
|
38
|
-
- `engine/topologies/platform/docker/dockerfile/dockerfile.star` and `generators/golden_docker_generator_v2.star`: default `cmd` parameters.
|
|
39
|
-
- `engine/topologies/platform/docker/build/golden_image_dockerfile.star`: hugo removal, healthcheck interval, migrator healthcheck.
|
|
40
|
-
- `engine/topologies/platform/docker/compose/compose.star`: compose healthcheck (lines 141, 264) reads the shared config; `init: true`.
|
|
41
|
-
- **CLI:** `cli/src/commands/doctor.ts` gets a Docker Engine / Compose minimum-version check.
|
|
42
|
-
- **Generated projects:** every project needs `tdk project --yes` (re-vendoring `.tdk/.tdk-out/tdk-cli-ext/`) and a golden image rebuild to pick up the change. Existing images keep working until then.
|
|
43
|
-
- **Behaviour:** after a failure, Docker marks a container unhealthy up to about 20 s later than today. Tilt readiness and dependency ordering still use the start period.
|
|
44
|
-
- **Tests:** starlark and generated-Dockerfile snapshot tests covering these files need updating.
|
package/openspec/changes/optimize-runtime-docker-layers/specs/runtime-container-footprint/spec.md
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# Spec Delta
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Keeps the per-container CPU, memory and image-size cost of generated TDK runtimes small enough that a large landscape (100+ resources) can run on a 16 GB developer laptop without starving the container runtime.
|
|
6
|
-
|
|
7
|
-
## ADDED Requirements
|
|
8
|
-
|
|
9
|
-
### Requirement: One Application Process Per Runtime Container
|
|
10
|
-
|
|
11
|
-
A generated Bun backend or Bun-static frontend runtime container SHALL run its application as a single Bun process. The container entrypoint MUST hand off to that process with `exec` rather than keep a wrapper process alive. This applies when the service's `start` script runs a single entry file.
|
|
12
|
-
|
|
13
|
-
#### Scenario: Default backend start script
|
|
14
|
-
|
|
15
|
-
- **WHEN** a generated backend's `package.json` has `"start": "bun run dist/index.js"` and its runtime container has finished starting
|
|
16
|
-
- **THEN** exactly one Bun process is running in the container (as listed by `docker top`)
|
|
17
|
-
- **AND** that process is running `dist/index.js`
|
|
18
|
-
|
|
19
|
-
#### Scenario: Custom start script is preserved
|
|
20
|
-
|
|
21
|
-
- **WHEN** a service's `start` script does more than run a single entry file (for example it chains commands or sets environment variables)
|
|
22
|
-
- **THEN** the runtime starts the service through `bun run start`
|
|
23
|
-
- **AND** the service behaves as it did before this change
|
|
24
|
-
|
|
25
|
-
#### Scenario: Signals reach the application
|
|
26
|
-
|
|
27
|
-
- **WHEN** a running backend container is stopped with `docker stop`
|
|
28
|
-
- **THEN** the application process receives SIGTERM directly and the container exits within the stop timeout
|
|
29
|
-
|
|
30
|
-
### Requirement: Healthchecks Are Low-Cost And Low-Frequency
|
|
31
|
-
|
|
32
|
-
Container healthchecks generated for services and golden images SHALL default to an interval of 30 seconds, and the timing MUST be overridable per run through `TDK_HEALTHCHECK_*` environment variables. A healthcheck command MUST NOT download packages or start a package-runner process (such as `bunx` or `npx`).
|
|
33
|
-
|
|
34
|
-
#### Scenario: Default service healthcheck interval
|
|
35
|
-
|
|
36
|
-
- **WHEN** a project is generated with default healthcheck configuration
|
|
37
|
-
- **THEN** every generated compose service healthcheck has `interval: 30s`
|
|
38
|
-
- **AND** the start period is at least as long as it was before this change
|
|
39
|
-
|
|
40
|
-
#### Scenario: Startup readiness is not delayed
|
|
41
|
-
|
|
42
|
-
- **WHEN** a generated service container starts and begins answering its health endpoint
|
|
43
|
-
- **THEN** Docker reports it healthy within 5 seconds of the first successful answer, even with a 30-second steady-state interval
|
|
44
|
-
|
|
45
|
-
#### Scenario: Configured interval is honoured
|
|
46
|
-
|
|
47
|
-
- **WHEN** `tdk up` runs with `TDK_HEALTHCHECK_INTERVAL_SECONDS` set (e.g. `15`)
|
|
48
|
-
- **THEN** the generated compose files and golden images use that interval
|
|
49
|
-
|
|
50
|
-
#### Scenario: One-shot migrator images carry no healthcheck
|
|
51
|
-
|
|
52
|
-
- **WHEN** the golden migrator image or a generated migrator runtime image is inspected
|
|
53
|
-
- **THEN** it declares no healthcheck (`HEALTHCHECK NONE`)
|
|
54
|
-
- **AND** no healthcheck anywhere in the generated images invokes `bunx` or `npx`
|
|
55
|
-
|
|
56
|
-
### Requirement: Golden Images Contain Only Universally Required Tooling
|
|
57
|
-
|
|
58
|
-
A golden runtime image SHALL contain only tooling that every service of that runtime type needs. Tooling that only some services use MUST be installed in the per-service layer, and only when that service's manifest declares the matching feature.
|
|
59
|
-
|
|
60
|
-
#### Scenario: Backend golden image has no hugo
|
|
61
|
-
|
|
62
|
-
- **WHEN** the golden L4 backend Bun image is built
|
|
63
|
-
- **THEN** the `hugo` binary is not present in the image
|
|
64
|
-
|
|
65
|
-
#### Scenario: Service declaring the hugo feature
|
|
66
|
-
|
|
67
|
-
- **WHEN** a service's manifest lists `hugo` in `featuresEnabled`
|
|
68
|
-
- **THEN** that service's runtime image contains the `hugo` binary
|
|
69
|
-
- **AND** services that do not declare it do not
|
|
70
|
-
|
|
71
|
-
### Requirement: Idle Backend Footprint Budget
|
|
72
|
-
|
|
73
|
-
A backend generated from the default TDK backend template SHALL, when idle (running and healthy, with no incoming requests), stay within 64 MiB container memory and average under 2% of one CPU core over 60 seconds, as reported by `docker stats`.
|
|
74
|
-
|
|
75
|
-
#### Scenario: Measuring an idle template backend
|
|
76
|
-
|
|
77
|
-
- **WHEN** a backend generated with `tdk resource <name> --type backend` has been running and healthy for 60 seconds with no incoming requests
|
|
78
|
-
- **THEN** its container memory usage is at most 64 MiB
|
|
79
|
-
- **AND** its average CPU usage over the following 60 seconds is below 2% of one core
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Tasks
|
|
2
|
-
|
|
3
|
-
## 1. Preflight checks
|
|
4
|
-
|
|
5
|
-
- [x] 1.1 Search every TDK project in the workspace (`tdk-erp-system`, `tdk-saas-starter`, `tdk-restaurant-example`, `tdk-user-management`) for services that use `hugo` without listing it in `featuresEnabled`. Record the result under design D5. Verify by attaching the search command and its (expected empty) output to the design note.
|
|
6
|
-
- [x] 1.2 Determine whether any `l4_migrator_golden`-based container runs long-lived or all are one-shot jobs. Record the answer under design D4 and keep or switch the D4 decision accordingly. Verify that design.md is updated with the evidence (compose `restart` policy and Tilt resource type).
|
|
7
|
-
|
|
8
|
-
## 2. Single-process runtime (D1, D2)
|
|
9
|
-
|
|
10
|
-
- [x] 2.1 Add one start-command resolver to the engine. It returns `bun <file>` for a `start` script of exactly `bun run <file>` or `bun <file>`, and `bun run start` otherwise. Verify with a new pytest in `tests/tilt-engine/` that generates Dockerfiles for fixture services with these start scripts: `bun run dist/index.js`, `bun dist/index.js`, `bun run dist/index.js --port 4000`, `NODE_ENV=x bun run dist/index.js`, `tsc && bun dist/index.js`, and no `start`. Assert the resulting `CMD` for each.
|
|
11
|
-
- [x] 2.2 Apply the resolver where every Bun `CMD` is emitted: the backend runtime (plain and Infisical-entrypoint paths) and the Bun-static frontend in `l4_runtime_layers.star`. Upstream `cmd` defaults stay as the no-override value and explicit overrides are kept (design D1). Verify with `test_backend_cmd_follows_start_script` (both paths) and `test_explicit_cmd_override_is_kept`.
|
|
12
|
-
- [x] 2.3 Emit `init: true` for generated backend and frontend runtime services in `compose.star`. Verify with a pytest asserting `init: true` on both service kinds in a generated compose file.
|
|
13
|
-
- [ ] 2.4 Build one real backend (`cash-management-api` in `tdk-erp-system`) from the new layers. Verify that `docker top` lists exactly one Bun process running `dist/index.js`, and that `docker stop` finishes in under 3 s.
|
|
14
|
-
|
|
15
|
-
## 3. Healthchecks (D3, D4)
|
|
16
|
-
|
|
17
|
-
- [x] 3.1 Set `get_docker_healthcheck_config` to `interval 30`, `start_interval 2`, `start_period 30`, `retries 3`, and add `--start-interval` to every golden `HEALTHCHECK`. Verify with a pytest on the generated `golden-layers.Dockerfile` asserting `--interval=30s --start-interval=2s` on each stage.
|
|
18
|
-
- [x] 3.2 Make `compose.star` (lines 141, 264) and `traefik_standalone.star` read the shared healthcheck config instead of hard-coding `10s`. Verify with a pytest asserting that generated compose files contain `interval: 30s` and `start_interval: 2s` and no `interval: 10s` for app services.
|
|
19
|
-
- [ ] 3.3 Replace the golden migrator `bunx prisma --version` healthcheck with `HEALTHCHECK NONE` (design D4). Verify with a pytest asserting that no generated golden stage or migrator runtime contains `bunx` or `npx` in a `HEALTHCHECK`, and that after a golden rebuild `docker inspect <prefix>-l4-migrator:latest` shows `Healthcheck.Test` = `["NONE"]`.
|
|
20
|
-
- [x] 3.4 Add a `tdk doctor` check for Docker Engine 25+ and Compose 2.20.2+ in `cli/src/commands/doctor.ts`, with vitest cases in `cli/src/commands/__tests__/doctor.test.ts` for an old version (fails, with an upgrade hint) and a current one (passes). Verify with `bun run test -- doctor.test.ts`.
|
|
21
|
-
- [ ] 3.5 Verify startup readiness with `docker events`: a freshly started backend is marked `healthy` within 5 s of its first successful `/health` response.
|
|
22
|
-
|
|
23
|
-
## 4. Golden image contents (D5)
|
|
24
|
-
|
|
25
|
-
- [ ] 4.1 Remove `RUN apk add --no-cache hugo` from the `l4_backend_bun` stage in `golden_image_dockerfile.star`. Verify that `docker run --rm <prefix>-l4-backend:latest which hugo` exits non-zero, and that `docker image inspect` shows the image about 57 MB smaller than before.
|
|
26
|
-
- [x] 4.2 Pass the manifest from `manifest_resource.star` to `Docker.backend` so the feature-gated install actually runs (design D5), then verify: a service with `"featuresEnabled": ["hugo"]` gets `apk add --no-cache hugo` in its generated Dockerfile and a service without it does not (`test_hugo_installed_only_for_services_declaring_it`).
|
|
27
|
-
|
|
28
|
-
## 5. Footprint budget and docs (D6)
|
|
29
|
-
|
|
30
|
-
- [ ] 5.1 Add `scripts/measure-idle-footprint.sh <container>`. It waits for the running backend container to be `healthy`, counts Bun processes, samples `docker stats` for 60 s, and exits non-zero if it finds more than one Bun process, memory over 64 MiB, or average CPU of 2% or more. (It measures an existing container instead of generating a template backend, because generation needs a whole project. The ERP backends are generated from the default template.) Verify it passes against a finance backend built from the new layers.
|
|
31
|
-
- [ ] 5.2 Document the minimum Docker Engine / Compose versions, the `TDK_HEALTHCHECK_*` overrides, the `featuresEnabled: ["hugo"]` migration note, and how to run the footprint script in `cli/README.md` (the repo has no root README). Verify the documented commands run as written.
|
|
32
|
-
|
|
33
|
-
## 6. Integration
|
|
34
|
-
|
|
35
|
-
- [ ] 6.1 Re-vendor into `tdk-erp-system` (`tdk project --yes`, touch the Tiltfile, rebuild golden layers) and run `tdk up finance`. Verify that every finance service is healthy and answers HTTP 200, and record per-container idle memory next to the baseline in proposal.md.
|
|
36
|
-
- [ ] 6.2 Measurement only: start the full `tdk-erp-system` landscape on the 16 GB reference machine. Record load average, free memory, and `docker ps` latency against the 2026-09-25 baseline, then add the numbers to proposal.md. This informs the separate default-startup-scope change and is not a pass/fail gate.
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# Design
|
|
2
|
-
|
|
3
|
-
## Context
|
|
4
|
-
|
|
5
|
-
See `proposal.md` (Why) for the motivating case. Relevant current behavior:
|
|
6
|
-
|
|
7
|
-
- Whether an app resource starts automatically is already a single Tilt-native, landscape-wide switch: `RuntimeFlags.resolve()` (`engine/topologies/tilt/resources/orchestrator/apply_runtime_flags.star`) reads `TILT_AUTO_INIT_APPS` into `runtime_flags['auto_init_apps']`, and `_register_replicas(config, auto_init_apps)` (`engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star:117`) passes it straight through as `dc_resource(..., auto_init=auto_init_apps)`. `auto_init` is Tilt's own parameter: `auto_init=False` means Tilt builds the resource's image and creates its container as part of reconciliation, but does not start it until explicitly triggered (`tilt trigger <resource>`, or the Tilt UI/HTTP API). This is exactly the "built but not started" split the proposal needs — it already exists, just as one global flag instead of a per-resource one.
|
|
8
|
-
- Sablier's premium implementation (`tdk-cli-extensions/premium/networking/sablier_container_cycle.star`) already assumes the container exists (Traefik's `traefik.docker.allownonrunning=true` plus `sablier.enable`/`sablier.group` labels) and only handles the docker-level stop/start of an existing container. It has no concept of Tilt's own build/create step, and no way to call back into Tilt.
|
|
9
|
-
- `engine/lifecycle/orchestrator.star`'s `STARTUP_PHASES` and `should_wait_for_dependencies` compute Tilt `resource_deps` chains per manifest (database, NATS) already; that same data is what a wake needs to decide which other resources to bring up together.
|
|
10
|
-
- Tilt exposes an HTTP API on `localhost:10350` (already referenced as the debug/inspection endpoint in `engine/paths.star:652` and as `tilt trigger <resource>` in `cli/src/utils/doctor-runtime.ts:486`) that includes a trigger endpoint equivalent to the `tilt trigger` CLI command. This design relies on that existing mechanism rather than adding a new one to Tilt.
|
|
11
|
-
- Real Sablier (`sablierapp/sablier`, already vendored per `traefik_standalone.star`) already holds an incoming request open and serves a wait/loading response until the container it started passes its healthcheck, or a configured timeout elapses — this is stock Sablier behavior, not something this change adds. What's missing is only: (a) getting the container into a "created, stoppable" state before it's ever run once, and (b) also reaching into Tilt for anything Sablier's plain Docker start doesn't cover.
|
|
12
|
-
|
|
13
|
-
**Verified against the live `tdk-erp-system` landscape and an isolated probe (2026-09-26), replacing the preflight guesses below:**
|
|
14
|
-
- The user's exact URL, reproduced live: `curl http://api.tdk-erp-system.localhost/api/data-governance/health` returns `404 page not found`, and Traefik's own API confirms why — `GET /api/http/routers/data-governance-api-project@docker` returns `{"message": "router not found: data-governance-api-project@docker"}`. The container (`analytics-autogenerated-data-governance-api-1`) does exist, `docker inspect` shows it ran once, went `unhealthy`, and `exited` hours ago — Tilt's own `uiresource` for it shows `runtimeStatus: pending` (queued to rebuild, `order: 50`, behind whatever else is ahead of it). Traefik drops an exited container from routing entirely because its labels have no `traefik.docker.allownonrunning=true` (this project has no `sablier` block on it at all). So the concrete failure mode observed in production is "crashed and queued for rebuild behind everything else," not only "literally never started" — D3's Tilt-trigger call addresses both, since it doesn't care why a resource isn't running, only that it now should be first.
|
|
15
|
-
- A throwaway `Tiltfile` + `docker-compose.yml` (two `busybox` services, one `dc_resource(auto_init=True)`, one `dc_resource(auto_init=False)`, run on an isolated port so the live landscape was untouched) showed: the `auto_init=False` resource gets **no container object at all** (`docker ps -a` shows nothing for it; its `uiresource` stays `runtimeStatus: none` / `updateStatus: none`) until triggered. `tilt trigger <name>` then creates *and* starts it from nothing in one call. This overturns D2's "Tilt pre-creates the container" assumption below — it does not. See the corrected D2/D3/D6.
|
|
16
|
-
- Read against the real `sablierapp/sablier` v1.18 docs (matching the vendored version): Sablier has **no webhook/event-hook system at all**. Its entire model is pull-based — the reverse-proxy plugin asks Sablier's HTTP API for a session's status on every request, and Sablier itself asks the provider (Docker) for instance status; the "blocking strategy" (already assumed above) is exactly this loop held open until ready or timeout. There is nothing to attach a "also call this other URL" hook to.
|
|
17
|
-
- The same docs describe two *native* mechanisms this design should reuse instead of inventing its own: (1) `sablier.group` — "start related workloads together... put an app and the database it needs in the same group, and a single request warms both at once" — but it requires every group member to itself carry `sablier.enable=true`; (2) Compose's own `depends_on` with `condition: service_healthy` / `service_completed_successfully` / etc. — Sablier reads the `com.docker.compose.depends_on` label Compose writes automatically and recursively starts+waits on dependencies, **even ones with no `sablier.*` labels at all**. TDK's generated compose deliberately omits `depends_on` today (`compose.star:298`: `# Note: depends_on removed - Tilt manages service startup order`) because Tilt's per-resource `resource_deps` already sequences non-deferred resources; that omission doesn't help a *deferred* resource, whose startup Tilt is explicitly not managing. See the corrected D2/D3/D4/D5.
|
|
18
|
-
- A second isolated probe (2026-09-26, same technique as the `auto_init` one, fully torn down afterward) tested what happens when a `deferStart`-shaped resource's compose entry has `depends_on: {other: {condition: service_healthy}}` and *both* resources are `auto_init=False`: Tilt itself parses the compose `depends_on` and automatically adds a Tilt-level wait — triggering the dependent resource alone leaves it parked at `runtimeStatus: pending` with `status.waiting = {on: [<dependency>], reason: "waiting-for-dep"}` **indefinitely**; Tilt does not auto-trigger the dependency just because something waits on it. Only once the dependency was triggered separately did the wait clear and the dependent resource actually build and start. So `depends_on` alone is not sufficient to reproduce D5's "wake the dependency too" requirement under Tilt (it would work if these were being started via a bare `docker compose up`, but Tilt's own reconciliation gates on its own trigger state first) — the wake gateway (D3) must explicitly `tilt trigger` every `depends_on` dependency itself, not rely on the cascade. This confirms and promotes what was recorded as a fallback to the primary, required mechanism.
|
|
19
|
-
- **A third finding overturned D5 entirely rather than just refining it:** `depends_on` is not just gated on Tilt's trigger state, it cannot validly reference a dependency at all in TDK's actual layout. `docker compose config` fails outright — `service "X" depends on undefined service "Y": invalid compose project` — whenever the referenced service is defined in a compose file registered via a *different* `docker_compose()` call, even one sharing the same `project_name` (tested with two files under one shared `project_name`: still rejected, confirming Tilt validates each registered file independently rather than merging same-project files for this purpose). Every real dependency a TDK resource has (database, NATS, its own migrator per `apply_migrator_orchestration.star:91`'s separate `docker_compose()` call) lives in a file generated by a different call than the resource's own. So `depends_on` cannot express any of D5's cases here, regardless of scoping — see the corrected D5.
|
|
20
|
-
|
|
21
|
-
## Goals / Non-Goals
|
|
22
|
-
|
|
23
|
-
**Goals:**
|
|
24
|
-
- Make "start on first request" work for a resource that has never run this session, using the same manifest opt-in, Traefik wiring, and license gate as today's Sablier feature.
|
|
25
|
-
- Reuse Tilt's existing `auto_init` mechanism per-resource instead of inventing a parallel scheduler.
|
|
26
|
-
- Keep the free-tier stub/premium-override structure (`sablier_container_cycle.star` in both repos) intact - this is an extension of that module's contract, not a replacement.
|
|
27
|
-
|
|
28
|
-
**Non-Goals:**
|
|
29
|
-
- Redesigning Tilt's build/reconciliation loop or its trigger API. See proposal.md Non-goals for the licensing and footprint boundaries.
|
|
30
|
-
- Handling a resource whose `sablier` block is added or toggled while `tdk up` is already running (that already requires a Tiltfile re-eval today, same as any other manifest change).
|
|
31
|
-
|
|
32
|
-
## Decisions
|
|
33
|
-
|
|
34
|
-
### D1: Deferred start is `auto_init=False` scoped per-resource, driven by the manifest, not the global flag
|
|
35
|
-
|
|
36
|
-
Add a `deferStart` key to the manifest's existing `sablier` block (`engine/topologies/tilt/manifest/schema.star`'s `sablier` schema, alongside `enable`/`group`/`sessionDuration`), default `false`. `_register_replicas` (and any other call site that currently receives the single `auto_init_apps` flag for app resources) resolves per-resource auto-init as `auto_init_apps and not resource_defers_start(manifest)` instead of using `auto_init_apps` directly. The resource's **config-gen** and **docker_build** resources keep `auto_init=True` unconditionally (or follow `auto_init_config_gen`, already `True` by default) so the image still builds during `tdk up` — only the running container is held back.
|
|
37
|
-
|
|
38
|
-
**Corrected live (2026-09-27, task 7.2/7.7):** that last sentence assumed wrongly. In this codebase `docker_build` has no `auto_init` of its own: Tilt attaches the image target to the `dc_resource` that deploys it, so `auto_init=False` skips the image build too. A `tdk up` on `tdk-erp-system` recorded zero builds for the deferred `data-governance-api`. **Fix:** each `deferStart` resource also gets a `<name>-image` `local_resource` (auto-init follows the landscape-wide `auto_init_apps`, not the deferred flag) that runs `docker build --target production --network host` into the tag the compose service declares. A plain `docker build` has no `only=` filter, so it builds from a copy of the Dockerfile (`<Dockerfile>.prebuild`) whose Dockerfile-specific `.dockerignore` is an allowlist generated from the same `only=`/`ignore=` lists `docker_build` uses (`TypescriptBuilders.prebuild_dockerignore`). Verified on `data-governance-api`: 14 kB build context, every layer served from the cache Tilt's own build populated, and filesystem layers identical to Tilt's image (only Tilt's `dev.tilt.*` labels differ). Tilt's own `docker_build` stays registered, so the first `tilt trigger` still gets live update.
|
|
39
|
-
|
|
40
|
-
*Alternatives considered:*
|
|
41
|
-
- A separate global "cold landscape" mode (e.g. `TILT_DEFER_ALL_APPS`): rejected because it can't express "just this one resource," which is the actual request (one API, not the whole landscape).
|
|
42
|
-
- Piggybacking on `TILT_TRIGGER_MODE=manual` wholesale: rejected — that already exists and disables `enforce_migrator_deps` and replica counts landscape-wide; overloading it would change unrelated behavior for every resource, not just opted-in ones.
|
|
43
|
-
|
|
44
|
-
### D2: Two genuinely different wake mechanisms, chosen by whether a container object exists
|
|
45
|
-
|
|
46
|
-
Confirmed live: `auto_init=False` leaves **no container object** until triggered — there is nothing for Traefik's Docker provider to discover (no container, no labels, no route), which is a different situation from a resource that ran before and is now `exited` (that container and its labels already exist; Traefik would route to it if and only if `traefik.docker.allownonrunning=true` is present). Docker-label auto-discovery — the mechanism the existing premium Sablier module relies on entirely — can only ever cover the second case. The two cases need different plumbing:
|
|
47
|
-
|
|
48
|
-
- **Previously-ran, now-stopped/exited** (the case actually reproduced live against `data-governance-api`): today's premium `sablier_container_cycle.star` labels already work once a resource opts in — `allownonrunning=true` is the only missing ingredient, and it's already emitted when `sablier.enable` is set. No new mechanism needed here beyond what D1/D4/D5 add.
|
|
49
|
-
- **Never created this session** (the state proven by the `auto_init_probe` test): there is no container to attach Docker labels to, so the route has to come from somewhere else. See D6.
|
|
50
|
-
|
|
51
|
-
*Alternative considered:* add an explicit `docker compose create <service>` step (without starting) right after image build, so every deferred resource always has a discoverable-but-stopped container from the start, keeping Docker-label discovery as the single mechanism for both cases. Rejected as the primary path: it re-adds exactly the container-creation cost (network setup, volume mounts) to `tdk up`'s critical path that deferring was meant to avoid, just shifted from "start" to "create," and it still needs D6's static route anyway for the moment between `tdk up` finishing and that create step actually completing. Recorded as a possible later optimization (pre-create eagerly to shave latency off the very first request) but not required for this change to work.
|
|
52
|
-
|
|
53
|
-
### D3: One wake gateway in front of every deferred resource; it starts the resource directly when possible, falling back to Sablier's API or `tilt trigger`
|
|
54
|
-
|
|
55
|
-
Since Sablier has no hook to piggyback on (see Context), the component that also needs to call Tilt was always going to be something this project writes, not a Sablier extension point. So rather than splitting "Sablier handles it, plus something else calls Tilt on the side," one small always-on gateway sits in front of every `deferStart` resource (behind D4's static route) and branches on whether a container object exists for it:
|
|
56
|
-
|
|
57
|
-
- **Container exists (previously ran, now stopped/exited):** the gateway calls Sablier's own HTTP API (the same call Traefik's plugin would make) to get the existing Docker-label wake, and in parallel runs `tilt trigger <resource>` so Tilt's own state (e.g. a migrator that needs to re-run) catches up too — Sablier's Docker-level start alone doesn't guarantee that. In practice this branch is reached rarely: per D4's verified priority fix, once any container object exists at all, Traefik's Docker-label route (at its normal, higher priority) wins over this gateway's static route on the very next request, so ongoing traffic to an existing-but-stopped resource goes straight to Sablier's own native plugin, not through here. This branch mainly covers the brief window before Traefik's docker provider notices a just-created container.
|
|
58
|
-
- **No container exists yet:** there is nothing for Sablier to manage (confirmed in Context — Sablier's Docker provider needs a labeled container to register, same constraint as Traefik's). This is the branch that matters for the proposal's actual motivating case (task 7.2's live `tdk-erp-system` run exercises exactly this), and it is **not** simply "call `tilt trigger` and wait" — see the queue-contention finding below. When the generated route carries the resource's compose invocation (project, compose files, env file, image — added by task 7.2's fix; see the note at the end of this section), the gateway runs `docker compose up -d --no-build <resource>` itself, the same command Tilt's own `<resource>-run-only` local_resource would run, using an already-built image. Only when no image exists yet (nothing has ever built this resource) does it fall back to `tilt trigger <resource>`, which both builds and starts it. Either way it then polls the resource's healthcheck itself, holding the request exactly the way Sablier's blocking strategy would.
|
|
59
|
-
|
|
60
|
-
Either way, the gateway is the single thing the static route (D4) points at, and it's the same process regardless of which branch it takes — this only affects which downstream call it makes on a given request. In both branches, the gateway also explicitly `tilt trigger`s and polls every dependency named in the request's `X-Wake-Deps` header (D2/D4), rather than assuming either Sablier or Tilt cascades that on its own — confirmed necessary by the second probe in Context (Tilt parks a triggered resource on `waiting-for-dep` indefinitely if its dependency is itself untriggered) and doubly so by D5's finding that Compose `depends_on` cannot even validly express these dependencies in TDK's layout, so nothing but the gateway's own explicit handling was ever going to cover this.
|
|
61
|
-
|
|
62
|
-
*Alternative:* always go through Tilt's trigger regardless of container state, and drop Sablier's Docker-level start entirely. Rejected: it makes every wake pay Tilt's reconciliation overhead even for the common case (a resource that only needs restarting), and it removes the one piece of this feature that doesn't depend on Tilt being reachable at all (Sablier + Traefik + Docker socket keep working even if the Tilt daemon itself is down, e.g. mid-`tilt up` restart). It also turned out to have a second, more serious problem, found only by testing against a real landscape rather than a small synthetic one (see below): `tilt trigger` only *queues* a resource — it does not run ahead of, or preempt, builds Tilt already has in flight.
|
|
63
|
-
|
|
64
|
-
**Found live against the real `tdk-erp-system` landscape (2026-09-27), not caught by any synthetic probe:** `tilt trigger` alone does not solve the proposal's actual motivating problem when the landscape is under load. Tilt caps concurrent builds (a handful of slots; observed 3 in this landscape), and a re-vendor or config regeneration that touches many manifests queues a rebuild for every affected resource. A `deferStart` resource triggered while ~100 other resources were mid-rebuild sat queued behind them; the request timed out with a `504` after the gateway's full wait budget with no container ever created — precisely the "wait behind hundreds of resources" behavior the whole feature exists to avoid, just moved one layer down (from `tdk up`'s bring-up order to Tilt's build-queue order). **Fix:** `apply_compose_resource_registration.star` now also passes the resource's compose invocation (its Compose project name, absolute compose file paths, `.env` path if present, and declared image tag) into `generate_static_wake_route`'s `X-Wake-Compose-*`/`X-Wake-Image` headers; the standalone Traefik compose (`traefik_standalone.star`) mounts the project root read-only into the gateway container at the same absolute path so those compose files resolve identically there. The gateway's `ensureImage()` also retags Tilt's own `<repo>:tilt-<hash>` build as the declared `<repo>:dev` tag when needed, since a resource can be fully built without ever having run once (so the plain `:dev` tag was never created) — confirmed against the live landscape's own image list. This start path only ever creates/starts a container from an image that already exists; it never builds, so it cannot race or duplicate a build Tilt has in progress, and a resource that has genuinely never been built at all still correctly falls back to `tilt trigger`.
|
|
65
|
-
|
|
66
|
-
**Implemented and verified live (2026-09-26), replacing speculation with what actually shipped:**
|
|
67
|
-
- The gateway is real: `engine/assets/docker/wake-gateway/` (`decide.ts` + `server.ts`, a Bun HTTP server; `Dockerfile` bundling Bun, the Docker CLI, and the exact pinned `tilt` binary version this workspace runs). Config for a request (which resource, which container, which dependencies, which port) travels via `X-Wake-*` headers that a generated Traefik `headers` middleware injects — not the request path — so the original path/query reach the real backend unchanged once ready.
|
|
68
|
-
- Confirmed live: `tilt trigger` from inside a container, reaching Tilt's HTTP port at `host.docker.internal:10350`, fails with `(403): invalid session token` even though the port is reachable — Tilt's HTTP API requires the same local session token its own CLI reads from `~/.tilt-dev/token` on the host. The gateway's compose service mounts `~/.tilt-dev` read-only; with that mount, the identical `tilt trigger` call succeeds.
|
|
69
|
-
- Confirmed live: the Tilt/Compose *resource* name (what `tilt trigger` and Compose's own `depends_on` use) is not the same string as the Docker *container* name Compose actually creates (`<project>-<service>-1`, e.g. `analytics-autogenerated-data-governance-api-1` for resource `data-governance-api`) — `docker inspect` needs the latter. The gateway and the generated route both carry both identifiers (`X-Wake-Resource` / `X-Wake-Container`) rather than assuming they're interchangeable.
|
|
70
|
-
- A full disposable rehearsal (isolated Tilt + Docker Compose + the built gateway image, torn down after) woke a cold `traefik/whoami` container from both "exited" and fully "absent" states and correctly proxied a request through with its original path intact.
|
|
71
|
-
|
|
72
|
-
**Verified live against `tdk-erp-system` (2026-09-27):** with the landscape quiet, the original URL answered `200` from a fully cold container in 19.2s via the direct compose start (task 7.2).
|
|
73
|
-
|
|
74
|
-
**Early `503` + `Retry-After` (task 7.8):** during a full `tdk up` bring-up the Docker daemon itself was the bottleneck: the direct start took ~5 min, and Bun's 255s idle cap dropped the held request with no response at all. Holding the request longer can't fix a saturated daemon, so the gateway now holds a waking request for at most 60s (`EARLY_RESPONSE_MS`, overridable via `X-Wake-Respond-Within-Ms`, capped at 240s), then answers `503` with `Retry-After: 10` while the start continues in the background. One wake runs per resource at a time: a retry, or another caller arriving meanwhile, joins the wake in progress rather than starting the container again. A resource that becomes healthy within the window is still answered directly, so the quiet-landscape case is unchanged.
|
|
75
|
-
|
|
76
|
-
### D4: A never-created resource's route comes from Traefik's static/file provider, not Docker-label discovery
|
|
77
|
-
|
|
78
|
-
Since a never-created resource has no container and thus no labels, its Traefik router has to be defined statically. The standalone Traefik generator (`traefik_standalone.star`) already reserves the `@file` provider namespace by reference (`maintenance@file` in `traefik.star`) but does not actually enable it — its `command:` list only sets `--providers.docker=true`; there is no `--providers.file=true` and no generated dynamic-config file or mounted directory today. This change adds both: `--providers.file.directory=/etc/traefik/dynamic` (a new generated directory mounted into the `traefik` service), and, for every resource with `sablier.enable` + `deferStart` set, a generated file-provider entry defining that resource's router/service/middleware ahead of time, pointed at D3's wake gateway.
|
|
79
|
-
|
|
80
|
-
**Verified live (2026-09-26, isolated Traefik+Docker probe, torn down after):** a file-provider router and a Docker-discovered router with the identical name coexist without conflict or error — Traefik namespaces them internally as `<name>@file` and `<name>@docker` — but when both match the same request at **equal priority, the file-provider router wins**, even though the Docker-discovered backend (the real, healthy container) was reachable and correct. A direct request through the file-route in that state hit the placeholder backend and got `502`, not the working one. Explicitly giving the file-provider entry a **low, fixed priority** (tested with `1`) while the Docker-label path keeps its existing computed priority (TDK's real values run 44-166, per `TRAEFIK_FRONTEND_PRIORITY_BASE + len(path)`) made the Docker-discovered router win as soon as it existed, with no manual removal of the static entry needed. So the static entry must carry an explicit low priority (a single reserved constant, e.g. `1`) — it is not safe to assume Traefik "prefers the more specific route" on its own; that was wrong.
|
|
81
|
-
|
|
82
|
-
*Alternative:* forward-declare the resource's Docker Compose service with `deploy.replicas: 0` or similar so a static route isn't needed. Rejected — Compose has no "define the service, register it with Traefik, but truly never create a container" mode short of the `auto_init=False` behavior already confirmed to produce zero Traefik-visible state; a static file-provider route is the only mechanism confirmed to give Traefik something to match against before any container exists.
|
|
83
|
-
|
|
84
|
-
### D5: Dependency wake is entirely the wake gateway's job — neither Compose `depends_on` nor a Sablier group works across TDK's actual project layout
|
|
85
|
-
|
|
86
|
-
Two mechanisms looked plausible from Sablier's docs (see Context) and were both ruled out by direct verification against TDK's real generated output, not just read about:
|
|
87
|
-
|
|
88
|
-
- `sablier.group`: rejected as previously recorded — it requires every member to itself be `sablier.enable`d, wrong for a deferred resource's database/NATS dependency, which is shared, always-on infra (`dc_resource(..., auto_init=True)` per `infra-loader.star`) that must never become eligible for scale-to-zero just because one deferred app joins its group.
|
|
89
|
-
- Compose `depends_on`: **verified live to be structurally impossible here, not just undesirable.** `docker compose config` rejects a service whose `depends_on` names a service not defined in that *same* compose file with `service "X" depends on undefined service "Y": invalid compose project` — confirmed both for a resource depending on a genuinely separate compose project (e.g. shared postgres) and, more surprisingly, for two resources sharing the same `project_name` but registered via two separate `docker_compose()` calls (e.g. a backend and its own migrator, per `apply_migrator_orchestration.star:91`'s own separate `docker_compose(..., project_name=compose_project_name)` call): Tilt runs `docker compose config` against each registered file *individually*, so a shared `project_name` string does not merge them into one validatable project. Since every one of a resource's real dependencies (database, NATS, its own migrator) lives in a compose file generated by a *different* `docker_compose()` call than the resource's own, `depends_on` cannot express any of them, full stop — there is no scoping that makes this workable, unlike D5's original assumption.
|
|
90
|
-
|
|
91
|
-
Dependency waking is therefore entirely the wake gateway's job, which was already built to not need either mechanism: `X-Wake-Deps` (D2/D4) carries each dependency's resource and container name directly, and the gateway `tilt trigger`s and polls each one itself (confirmed in the live rehearsal in D3). This is strictly simpler than the original plan, not a workaround — one mechanism, already implemented and tested, instead of two speculative ones layered together. The practical scope this actually covers is narrower than first assumed, too: database/NATS are always-on infra that's essentially never actually cold, so the gateway's dependency-waking mostly matters for the rarer case of one deferred resource depending on another.
|
|
92
|
-
|
|
93
|
-
*Consequence:* Sablier's native docker-level start path (the "container exists" branch in D3) has no dependency-awareness of its own — it can restart a container but can't wait on that container's migrator or anything else via a label Compose never had a valid reason to write. In practice this rarely bites: by the time any container object exists at all, D4's priority verification means Traefik routes to it directly via the Docker-label path (Sablier's own plugin), bypassing the gateway entirely — the gateway's own "container exists" branch is reached only in the brief window before Traefik's docker provider notices a just-created container's labels, a correctness safety net rather than the primary path for that case.
|
|
94
|
-
|
|
95
|
-
*Alternative:* the originally-planned computed `sablier.group`, and separately, generated `depends_on`. Both rejected once the real mechanics were confirmed — the former for the scale-to-zero risk above, the latter because it is not merely undesirable but structurally rejected by `docker compose config` for every real dependency shape in this codebase.
|
|
96
|
-
|
|
97
|
-
### D6: `tdk up`/`tdk doctor` read `auto_init` state, not container status, to report "deferred"
|
|
98
|
-
|
|
99
|
-
A deferred resource looks, from `docker ps`, identical to a resource that failed to start. `tdk up`'s summary and `tdk doctor` need to check the manifest's resolved `deferStart` (D1) directly rather than inferring "deferred" from container state, so a genuinely stuck resource is never miscategorized as intentionally cold.
|
|
100
|
-
|
|
101
|
-
## Benchmark: D3's queue-bypass fix, measured
|
|
102
|
-
|
|
103
|
-
Task 7.4's live `tdk-erp-system` finding (D3) was that `tilt trigger` alone re-creates the original problem one layer down: a triggered resource queues behind Tilt's build-slot cap. That fix (direct `docker compose up --no-build` when an image already exists) needed a real, reproducible measurement, not just the one live pass that got interrupted by the strip-prefix bug — but re-testing against the real `tdk-erp-system` landscape wasn't available (developer's own call, 2026-09-27: stop hammering an already-overloaded shared Docker daemon). Instead, ran a disposable, isolated benchmark (own Tilt instance on a private port, own Compose project, fully torn down after — no interaction with any real project) that exercises the actual shipped `generate_static_wake_route()`/`decide.ts`/`server.ts` code, not a reimplementation:
|
|
104
|
-
|
|
105
|
-
- **Setup:** `update_settings(max_parallel_updates=2)` plus six "filler" `dc_resource`s with a genuinely slow (90s, uncached each run) Docker build, standing in for a large landscape's build queue — this is the same build-slot contention Tilt showed live (`max_parallel_updates` caps concurrent builds; the real landscape's default cap was observed at 3). Two target resources, both `auto_init=False`: `target-fast`'s route carries the compose-invocation headers (`X-Wake-Compose-*`/`X-Wake-Image`) with its image pre-tagged, exercising the new direct-start path; `target-slow`'s route omits them and has its own `docker_build`, so its first `tilt trigger` must acquire a build slot like the fillers, exercising the pre-7.4 fallback path unchanged.
|
|
106
|
-
- **Results** (wall-clock, cold container to first `200 OK`, via `curl` through the real Traefik static route and gateway):
|
|
107
|
-
|
|
108
|
-
| Scenario | Build-slot contention | Time to 200 |
|
|
109
|
-
|---|---|---|
|
|
110
|
-
| `target-fast` (direct compose start) | 2/2 slots busy (fillers) | **12.6s** |
|
|
111
|
-
| `target-fast` (direct compose start) | idle | 7.4s |
|
|
112
|
-
| `target-slow` (`tilt trigger` fallback) | 1/2 slots busy | 18.1s |
|
|
113
|
-
| `target-slow` (`tilt trigger` fallback) | idle | 22.8s |
|
|
114
|
-
|
|
115
|
-
- **What this shows:** `target-fast`'s latency stayed in a narrow band (7-13s) regardless of queue state — direct confirmation that the new path doesn't touch Tilt's build queue at all, matching the gateway's own log line (`compose start target-fast: started`, no `tilt trigger` call). `target-slow` paid a real, unconditional cost even at its best (idle) case — nearly double `target-fast`'s idle case — because every `tilt trigger` still goes through Tilt's own build-reconciliation step (confirmed via Tilt's build history for it) before `docker compose up` runs; that step is exactly what queues behind other builds live against `tdk-erp-system` (task 7.4's original 504). A fully apples-to-apples "both paths, both fully saturated" run wasn't captured (the 90s filler window and Tilt's own infra-resource build ordering made catching that exact instant unreliable without further burning the same finite build-queue setup repeatedly) — the mechanism-level proof (gateway logs showing which code path ran) plus the consistent directional numbers above are treated as sufficient verification, not a substitute for eventually re-running task 7.2's exact live URL once `tdk-erp-system`'s own queue is quiet.
|
|
116
|
-
|
|
117
|
-
## Risks / Trade-offs
|
|
118
|
-
|
|
119
|
-
- [The static file-provider route (D4) and the Docker-discovered route disagree once the container exists, e.g. stale healthcheck path baked into the static entry] → Keep the static entry's routing rule byte-identical to what the generator already emits for the Docker-label version (same source-of-truth function), so the two can never drift; verified by generating both and diffing in task testing.
|
|
120
|
-
- [Confirmed live: network reachability to Tilt's port alone isn't enough — `tilt trigger` from inside a container fails with `(403): invalid session token` without the host's `~/.tilt-dev` token] → Fixed by mounting `~/.tilt-dev` read-only into the wake-gateway service. If Tilt runs in a context where that directory isn't at the expected host path (e.g. Tilt itself inside a devcontainer), the mount is wrong and every trigger call fails the same way — surfaces as a clear 403 in the gateway's logs rather than a silent hang, but there's no auto-detection of an unusual `$HOME` yet.
|
|
121
|
-
- [Re-adding `depends_on` to a `deferStart` resource's compose entry (D5) has some other effect under Tilt's per-resource `docker compose ... --no-build` invocation that the original removal was guarding against] → Verify by generating and running a `deferStart` resource with `depends_on` present under normal (non-deferred) `tdk up` handling, confirming Tilt's own bring-up for it is unaffected — expected to be moot in practice since a `deferStart` resource's `auto_init=False` means Tilt never invokes it directly during `tdk up` anyway, but confirm rather than assume.
|
|
122
|
-
- [A `deferStart` resource's `depends_on` condition (`service_healthy`/`service_completed_successfully`) never resolves because the dependency is itself unhealthy or the migrator's `honor-restart-policy` isn't enabled] → Sablier's docs are explicit that `service_completed_successfully` needs `--provider.docker.honor-restart-policy=true` to recognize a one-shot migrator's exit code 0 as `completed` rather than `stopped`; enable that flag on the vendored `sablier` command alongside D5's `depends_on` change, and hold the wake request only up to the existing timeout rather than indefinitely if it never resolves.
|
|
123
|
-
- [The held request during wake exceeds Sablier's own timeout while waiting on a dependency chain] → `sessionDuration`/timeout stay configurable per resource via the existing manifest field; document that a resource with dependencies needing time to become healthy may need a longer timeout.
|
|
124
|
-
- [Background auto-commit tooling observed in this workspace pushes partial engine edits] → Land each decision's Starlark and CLI edits together with its snapshot tests in one sitting, per the same mitigation already used in `optimize-runtime-docker-layers`.
|
|
125
|
-
- [Found and fixed 2026-09-26: re-reading the shipped `apply_compose_resource_registration.star` found the `generate_static_wake_route(...)` call site never passed `dependencies=`, so D5's dependency-wake path was implemented and unit-tested (gateway side and generator side both) but never wired to any real dependency data — every generated route shipped `X-Wake-Deps: ""`] → Fixed (task 7.3): the call site now resolves a `deferStart` resource's manifest `dependsOn` entries to the subset that are themselves `deferStart`, reusing the same resolution `_build_resource_deps` already trusts for Tilt's own `resource_deps` ordering, so it carries no new naming risk beyond what's already shipped. Covered by 6 new Starlark tests. The full licensed, live "wakes a cold peer resource" path is still only verifiable against the real premium module (task 7.2), consistent with how task 3.1 already treats that limitation.
|
|
126
|
-
|
|
127
|
-
## Migration Plan
|
|
128
|
-
|
|
129
|
-
1. Ship the schema (`deferStart`) and engine changes (D1-D6) in a TDK release, gated the same way the rest of `sablier` already is (premium license required for the real module; free-tier stub keeps returning disabled).
|
|
130
|
-
2. In each project with an opted-in resource: run `tdk project --yes` to re-vendor, touch the Tiltfile, rebuild affected images.
|
|
131
|
-
3. A resource adopts deferred start by adding `deferStart: true` to its existing `sablier` block; removing it (or the whole block) reverts to today's behavior with no other cleanup needed.
|
|
132
|
-
|
|
133
|
-
**Rollback:** unset `deferStart` (or the whole `sablier` block) and re-run `tdk project --yes`; the resource returns to normal `auto_init` bring-up immediately.
|
|
134
|
-
|
|
135
|
-
## Open Questions
|
|
136
|
-
|
|
137
|
-
- Whether Traefik, when both a file-provider router (D4) and a Docker-discovered router (D2) exist for the same resource name, prefers one deterministically or requires a distinct router name per provider - resolved empirically in tasks.md's preflight phase (task 1.4); at most changes a naming detail, not the approach.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Proposal
|
|
2
|
-
|
|
3
|
-
## Why
|
|
4
|
-
|
|
5
|
-
On a large landscape (`tdk-erp-system`: 107 resources, 110 containers), a request to a specific backend — e.g. `http://api.tdk-erp-system.localhost/api/data-governance/health` — can go unanswered for minutes not because that backend is broken, but because Tilt hasn't reached it yet in its own bring-up order. `data-governance-api` has no `sablier` block today, so it is started the same way as every other resource: as part of the full `tdk up` sequence, behind however many of the other ~106 resources Tilt's dependency graph and build concurrency put ahead of it. The existing premium Sablier integration (`engine/topologies/platform/docker/networking/sablier_container_cycle.star`, real implementation in `tdk-cli-extensions/premium/networking/`) already wakes a resource on its next request — but only if Tilt already created and then stopped that resource's container this session. It has nothing to wake when the container was never created in the first place, which is exactly the case here. Developers working against one corner of a large landscape (one API, one flow) shouldn't pay the full-landscape startup cost, or sit wondering whether the resource they actually want is stuck or just queued.
|
|
6
|
-
|
|
7
|
-
## What Changes
|
|
8
|
-
|
|
9
|
-
- Extend the existing `sablier: {enable: true}` manifest block (opt-in, premium, unchanged licensing boundary) so an opted-in resource can be marked deferred for the *current* `tdk up` run, not only for scale-to-zero after it has run once. A deferred resource's image still builds during `tdk up` (so there's something to start on first request), but Tilt does not create or start its container at all, removing it from the sequential dependency-bring-up path entirely (confirmed empirically: an `auto_init=False` resource has no Docker container object until triggered — there is nothing for Traefik's normal Docker-label discovery to find).
|
|
10
|
-
- Because a never-created resource has no container and thus no Docker labels, give Traefik a static route for it via its file provider (a mechanism this codebase already references by name — `maintenance@file` — but doesn't yet enable for standalone projects), pointed at a small always-on wake endpoint. A resource that has previously run and is merely stopped keeps using today's Docker-label discovery (`traefik.docker.allownonrunning=true` plus Sablier's labels) unchanged.
|
|
11
|
-
- On the first request that hits a deferred resource's route, the wake gateway runs `tilt trigger <resource>` (Tilt's own supported CLI, proven to create-and-start a never-touched resource in one call) plus a separate `tilt trigger` for each dependency named in the request (confirmed necessary: Tilt does not cascade a trigger to a dependency on its own — it just waits on it forever), so the whole chain is prioritized immediately instead of waiting for Tilt's normal resource order. For a previously-run-then-stopped resource, Sablier's existing Docker-level start stays the primary (faster) path, with the same trigger calls fired alongside it so Tilt's own state (e.g. a migrator) catches up too.
|
|
12
|
-
- Dependency information for a wake travels through the gateway itself (which resource, which container, which dependencies), not through Compose's `depends_on` — confirmed empirically that `depends_on` cannot validly reference any of TDK's real cross-resource dependencies at all (database, NATS, a resource's own migrator each live in a separately-registered compose file; `docker compose config` rejects the reference outright even when two such files share a project name).
|
|
13
|
-
- `tdk up` and `tdk doctor` report deferred resources as intentionally cold (not stalled/pending), so their absence from the early startup log isn't read as a hang.
|
|
14
|
-
|
|
15
|
-
**BREAKING (none):** this only changes behavior for resources that opt in via the existing `sablier` manifest block; a resource without that block behaves exactly as it does today.
|
|
16
|
-
|
|
17
|
-
## Capabilities
|
|
18
|
-
|
|
19
|
-
### New Capabilities
|
|
20
|
-
- `on-demand-resource-startup`: Requirements for how a Sablier-opted-in resource that Tilt has not yet started this session is discovered by Traefik while stopped, triggered and prioritized on its first request (including its dependency chain), and reported by `tdk up`/`tdk doctor` as deferred rather than stalled.
|
|
21
|
-
|
|
22
|
-
### Modified Capabilities
|
|
23
|
-
- (none — no existing `openspec/specs/` capability documents today's Sablier behavior or Tilt's startup sequencing, so there is nothing to amend; this proposal documents the extended behavior as new.)
|
|
24
|
-
|
|
25
|
-
## Non-goals
|
|
26
|
-
|
|
27
|
-
- Changing the free/premium licensing boundary. This stays inside the existing `sablier` opt-in and its license gate (per product decision); it does not become default behavior for unlicensed or non-opted-in resources.
|
|
28
|
-
- Reducing per-container CPU/memory footprint — already addressed by `optimize-runtime-docker-layers`, whose non-goals explicitly deferred "putting backends under Sablier scale-to-zero" to this change.
|
|
29
|
-
- Building a general-purpose priority queue inside Tilt itself. This relies on Tilt's existing `TILT_AUTO_INIT_APPS`/manual trigger mode and its trigger API rather than patching Tilt's own scheduler.
|
|
30
|
-
- Changing behavior for resources already running and healthy, or for landscapes with no `sablier`-opted-in resources.
|
|
31
|
-
|
|
32
|
-
## Impact
|
|
33
|
-
|
|
34
|
-
- **Engine (Starlark):** `engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star` (today's landscape-wide `auto_init_apps`) needs a per-resource variant driven by each manifest's `sablier` block rather than one global flag.
|
|
35
|
-
- **Engine (Starlark):** `engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star` calls the new static-route generator for each `deferStart` backend resource and writes its output alongside the resource's other generated config.
|
|
36
|
-
- **Engine (Starlark):** `engine/topologies/platform/docker/compose/traefik_standalone.star` — adds `--providers.file` support (new command flag, mounted dynamic-config directory), generated static route entries for deferred resources, and a wake gateway service (built from new `engine/assets/docker/wake-gateway/`) that runs `tilt trigger` and proxies the held request through.
|
|
37
|
-
- **CLI:** `cli/src/utils/resource-features.ts` (`sablier` feature description), `cli/src/commands/up.ts` and `doctor-runtime.ts` reporting for deferred resources; `cli/src/types/index.ts`'s `sablier` manifest type gains the new field(s).
|
|
38
|
-
- **Manifest schema:** `engine/topologies/tilt/manifest/schema.star`'s `sablier` block gains a new optional key (name TBD in design, e.g. `deferStart`).
|
|
39
|
-
- **Generated projects:** every project with an opted-in `sablier` resource needs `tdk project --yes` (re-vendor) plus a golden image rebuild to pick up the change; existing non-opted-in resources are unaffected.
|
|
40
|
-
- **Tests:** `tests/tilt-engine/` snapshot tests for the per-resource `auto_init` resolution and the generated static Traefik route; `bun test` unit tests for the wake gateway's decision logic.
|
|
41
|
-
- **Engine (Starlark) — found live, 2026-09-27:** two real gaps only surfaced by testing against the actual `tdk-erp-system` landscape, both now fixed (design.md D3, tasks.md 7.4/7.5): (1) `tilt trigger` alone re-created the original problem one layer down (queues behind Tilt's own build-slot cap under real load) — the gateway now prefers a direct `docker compose up --no-build` when an image already exists; (2) a woken resource still 404'd because the wake route didn't strip the same `apiPath` prefix the real Docker-label router does before forwarding — fixed by adding the matching `stripPrefix` middleware to the generated route. A disposable, isolated benchmark (design.md's "Benchmark" section) measured the first fix's real effect: the new path answers in ~7-13s independent of build-queue contention, versus ~18-23s for the old fallback even at its best case.
|