feat(connect): opt-in local Kubernetes service deployment - #2404
feat(connect): opt-in local Kubernetes service deployment#2404agarwal-ishaan wants to merge 20 commits into
Conversation
…t regeneration
pdd generate had an asymmetry: `_verify_public_surface_regression` only
ast.parse()s generated Python when existing_code is present (i.e. only
when regenerating a mature module). A brand-new file has no existing
code to compare against, so that gate is skipped entirely -- a prompt
whose content doesn't match its declared `_<language>` filename suffix
(e.g. `game_python.prompt` whose content actually describes an HTML
page) can silently write unparseable content into a freshly created
`.py` file with no error at all.
Add `_verify_generated_syntax`, called unconditionally alongside the
existing `_verify_architecture_conformance` check (which already runs
on every generation, first-time included). It ast.parse()s the output
against its declared language and raises ArchitectureConformanceError
on mismatch, reusing the same error type/UX the codebase already has
for conformance failures. Respects PDD_SKIP_CONFORMANCE. Tolerates a
surface markdown code fence (some response paths intentionally leave
one un-stripped) so it validates the real code rather than tripping on
cosmetic ``` wrapping.
Also updates a handful of existing incremental-generation tests whose
mock "generated code" fixtures were plain English placeholder strings
("Updated code", "Base-ref updated code", etc.) rather than valid
Python -- harmless before this fix since nothing validated first-time
output, but now correctly caught by the new check. Replaced with
trivial valid-Python snippets; the tests' actual assertions (control
flow, kwargs passed, cost/model returned) are unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…k their prompt The repair_directive already told an agentic repair loop to check the prompt against its declared language, but the actual user-facing message (what a human sees on the CLI) didn't. Add an explicit, plain-language prompt to double-check the requested content matches the declared language suffix, since that's the most common real cause of this error (verified via manual end-to-end testing against a real prompt/response pair). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_verify_generated_syntax was added directly to code_generator_main.py (17048b3, 72afd9f) without updating its source-of-truth prompt. Add section 5a1 to code_generator_main_python.prompt spelling out the gate exactly as implemented, so the prompt and code stay in sync. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… to check their prompt" This reverts commit 72afd9f.
… not just regeneration" This reverts commit 17048b3.
Dev Units are implementation units, not pods. This adds an explicit mapping layer so a developer declares which Dev Units combine into an independently runnable service, then runs those services on a named local cluster. pdd-k8s companion package (pdd-k8s/): - .pdd/deployments.yaml maps dev_units -> dockerfile/port/health per service - doctor / services / up / status / logs / down / manifest commands - plain generated manifests, no Helm, no Dockerfile generation - kubectl pinned to the manifest's own context; only objects labelled app.kubernetes.io/managed-by=pdd are ever deleted - pdd_k8s.api is the stable facade Connect imports; it never raises Connect integration: - /api/v1/deployments router, registered unconditionally but degrading to plugin_installed: false so core PDD never depends on Docker or Kubernetes - deploys run as background operations polled via /operations, since container builds take minutes - DeploymentsPanel renders nothing unless the plugin and a manifest both exist Also corrects a stale expected __all__ in test_routes_init.py that omitted the observability exports.
Why Kubernetes, and why it might be the wrong callRecording the trade-offs explicitly, since #2403 asks whether this layer should exist at all rather than how to build it. AdvantagesThe declarative model already matches what PDD does. PDD's whole premise is that a prompt is the source of truth and the artifact is derived. Kubernetes has the same shape: a manifest is the source of truth and the running pod is derived. Readiness is a real signal, not a guess. A readiness probe is the difference between "the process started" and "the service actually works." That is exactly the question a developer has after regenerating a Dev Unit, and it is genuinely hard to answer without an orchestrator. This PR gets it almost free — Restarts and events turn silent failures into evidence. When a regenerated Dev Unit crashes on import, the pod restarts and Kubernetes records why. That trail is what makes the Connect panel useful rather than decorative — Multi-service projects stop needing a README ritual. Once a project has an API and a worker and a frontend, "how do I run this" becomes tribal knowledge. The labelling model gives safe blast-radius control. Everything PDD creates carries It is a credible path to parity with deployment. If a team later runs on Kubernetes for real, the local manifests are the same object model. Nothing learned locally is wasted. DisadvantagesThe dependency chain is heavy and fragile. Docker Desktop, plus kind, plus kubectl, plus a downloaded node image — before a single line of the user's code runs. This session proved the point: the Docker daemon wedged mid-demo and took the cluster with it, and every It is enormously more machinery than most projects need. For a single-process FastAPI app, The iteration loop is slow. Build the image, side-load it into kind, apply, wait for rollout. That is tens of seconds per change against sub-second hot reload. A developer iterating on one Dev Unit will not want this in the loop, so it competes with the workflow it is meant to support. It leaks a large vocabulary into PDD. Namespaces, selectors, probes, replicas, rollout status, events. The panel is deliberately narrow, but the moment something breaks, the user is debugging Kubernetes, not PDD. Every error message this layer surfaces is one PDD did not write and cannot control. Kubernetes semantics are not local-dev semantics. It risks defining the abstraction around one backend. The interesting idea in #2403 is "local service runner." If the schema, the panel and the vocabulary all harden around Kubernetes, adding Docker Compose later means retrofitting, not extending. Where that leaves itThe honest summary: Kubernetes is a good first backend and a bad only backend. It is the right choice for validating the concept, because it forces the hard questions — health, readiness, restarts, teardown scope — to be answered properly rather than approximated. It would be the wrong choice as a permanent assumption, because most PDD projects will never justify the dependency. Two concrete implications for review:
|
The routes __init__ prompt still specified only the four original routers, while the module exports nine submodules, eight routers and two project-scoped factories. The file is .pddignore'd so nothing regenerates it, which is how the drift went unnoticed, but an inaccurate spec is worse than none. Also documents why observability and deployments export factories rather than router objects: both are built per project root, not at import time.
Tracks #2403.
Adds an opt-in local deployment layer to PDD Connect. Dev Units are implementation units, not pods — a developer explicitly maps one or more Dev Units onto an independently runnable service. Projects that are plain scripts, CLIs or libraries are entirely unaffected.
What's here
pdd-k8s/— companion packageDeliberately a separate distribution. Core PDD never imports it; it is loaded lazily and its absence is a normal, reported state.
doctor·services·up·status·logs·down·manifestConnect integration
/api/v1/deployments— registered unconditionally, degrades toplugin_installed: falsewith an install hint. Action endpoints return501rather than failing to import./operations— container builds take minutes, so a synchronous POST would block.DeploymentsPanelrendersnullunless the plugin and a manifest both exist.Safety properties
kubectlcall is pinned to the manifest's own context (kind-<name>), so an unrelated cluster cannot be touched — asserted by a test that inspects every recorded invocation.app.kubernetes.io/managed-by=pddare deleted.:local; nothing is pushed to a registry.downleaves the cluster running unless--clusteris passed.Deliberate non-goals
Verification
Exercised end to end against a real kind cluster, deploying a FastAPI service whose five Dev Units ship as one container:
The service served real traffic in-cluster, and Connect's API returned the live mapping and pod health.
pdd-k8s: 54 tests, every external command faked — needs no Docker, kind or cluster.Two real bugs were caught and fixed during this work:
nodeNamewas read frompod.statusinstead ofpod.spec, and generated YAML emitted anchors (&id001) that made single objects hard to copy out.Known blockers — please read before merging
1. Sync-ownership gate.
test_estimate_contract_rotations_are_exact_and_dormantfails on 8 tracked paths with no rule in.pdd/sync-ownership.json. All 8 are pre-existing observability paths — verified identical with these changes stashed. The new deployments paths will need rules too. Since that file is itself digest-pinned, this reads as maintainer work rather than a contributor edit, so I have left it untouched.2. No fast-fail on a wedged Docker daemon. Requests return correct results but hang on subprocess timeouts (~20s for status, ~10s for logs).
runtime.pyneeds a cheap liveness probe. Tracked in #2403.Open question for maintainers
The real question is not "should PDD support Kubernetes?" It is whether PDD should offer an optional, project-aware local deployment layer at all. If yes, the abstraction should stay "local service runner" so a Docker Compose backend can slot in later. Discussion in #2403.