All notable changes to this project are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
A defect-fix release hardening the design lane and the per-story acceptance gate,
plus two capture/replay fidelity fixes. All fixes fold onto 0.3.7; no behavior is
removed. Legacy sftdd names/paths still read for back-compat.
- Persistence-invariant fitness tests now anchor to the story that REALIZES the
invariant, not the first story that merely names its table. The Test
Strategist previously front-loaded every persistence invariant (including those
for a not-yet-created table) onto an early display/read-only story's fitness
tests; the write story that actually migrates the table could then never obtain
a distinct coverage tag, and the story-scoped reflection revise could not clear
it (the fix required editing a different, already-gated story) — a dead-loop to
HIL. A new
invariantRealizingStoryprimitive joinsarchitecture.persistence_invariants[].tabletodb-design.schema_changes[]to resolve the realizing story; the fitness analyst prompt, the spec-gate distinct-coverage check, and the reflect remediation all key off it, so mis-anchoring is prevented at authoring time and, if it still occurs, is caught and correctly attributed at the story's own gate. - The per-story acceptance gate no longer serves an UNMIGRATED experiment
branch. Honest-GREEN verify runs the migration only on a disposable child
branch (to isolate a reversibility test's up/down fixtures), so the experiment
branch the deploy bound the review app to was never upgraded — the write path
500'd with
relation ... does not existeven though every test passed. The--gatedeploy now applies a forward-only, idempotent migrate (migrate:target, the language-agnostic entrypoint) to the bound branch BEFORE starting the app, and a failing migrate fails the deploy honestly (reachable:false+ escalation) instead of certifying a broken app. - Gate-deploy reachability is now a strict non-5xx serving probe. A booted-but-erroring app (e.g. one bound to an unmigrated branch) previously certified as "reachable" because any HTTP response counted as up; the acceptance gate now requires a non-5xx response. Non-gate deploys and the foreign-port guard keep the lenient probe.
- Story-scoped
review-verdictresolves at the cycles story-root (route/ contract seam fix), so a story-loop review no longer mis-resolves its path. - Replay reads the intake design-brief + assets from the finalize-corpus mirror
layout (nested
design/), fixing the live replay launcher after the corpus mirror moved where those assets land.
- agent-log.jsonl is mirrored into the corpus during a recording, so the central agent-log stream is preserved alongside correspondence for replay.
A large consolidation release: the orchestrator's internals were reorganized into
cohesive domain families, every agent turn now dispatches through one executor
seam, a per-role/per-turn optimization harness landed, the capture/replay corpus
tooling was rebuilt, and the whole kit was renamed from the internal sftdd
lexicon to Consort (legacy names/paths still read for back-compat).
- One executor dispatch path for every agent turn. Design, planning, and
build turns (spec-author breakdown, navigator RED/assess/review/reflect, driver
green/refactor/repair, and the deploy/superseded variants) all dispatch through
a single parameterized
ClaudeStepAgentvia a manifest-declaredagent:{kind,config}+ step-contract seam, replacing the per-turn command-builder arms. Each shipped step declares its agent and its inputs/outputs/route contract, with a pre-dispatch route-satisfiable check and a three-channel output model (product code /.consortartifact / meta). - Per-role and per-turn optimization harness. A committed sweep engine
experiments on each design and navigator build turn across model tiers and
effort/tool-scope levers, judged against the recorded reference output, with
visible results,
summary.json, baseline compare, and programmatic auto-apply of a winner into anoptimized-defaults.jsonoverlay. The test-strategist was rewritten as a supervisor over per-kind test analysts with their own levers. - Capture/replay corpus tooling. Faithful per-turn snapshot replay with an
honest live verify and a divergence guard; a browsable
.consortmirror with a retroactive path sweep; per-turn replay sets; a two-sprint/sprint-driven capture launcher that records the full orchestrator↔HIL correspondence; and a planning-only (--plan-only) capture/replay path. Thestockflow-fullandstockflow-planreference corpora were added. - Deterministic UI reachability + token-consumption gate with design-guide components, and an app-icon design-adherence check wired into the UX-clean gate.
- Renamed the kit from
sftddto Consort. Canonical symbols are nowresolveConsortSettings,consortEnv,LAKEBASE_CONSORT_*, the.consort/artifact root,.lakebase/consort-config.json, andconsort-*bin names. The tri-read env prefixes, legacy config/artifact-root fallbacks, andlakebase-*bin aliases still resolve, so projects scaffolded before the rename keep working. The scaffolded launcher is nowscripts/consort.sh. - Reorganized the source tree into domain families under
consort/(config, logging, gates, experiment, pipeline, smells, architecture, intake, deploy, orchestrator/{steps,runners,turns,state,workflow,drive,settings,build}, provisioning) with acyclic-import and single-home anti-recurrence guards; the executable CLIs moved tobin/consort/+bin/lakebase/. - Fact-checked and de-jargoned all documentation to the real symbols, and
swept stale
sftddnaming out of source comments, shell scripts, and test descriptions (functional legacy literals preserved). - navigator RED defaults to opus (model tiering, matching driver); per-invocation-step effort/model config is the single per-step home.
- Live-drive resilience: a stalled agent turn now self-heals (inactivity
timeout kills + retries instead of hanging), with the inactivity clock gated on
content lines; headless spawns use
acceptEdits(managed-settings downgrade the bypass mode); the build fails fast on expired Databricks auth. - Supersession / self-heal routing (classes k–n):
ctx.loopsourced from the config file so derive + effects agree; thesuperseded_testskey alias is tolerated; the green-superseded route reads the correct per-story test list; and asuperseded:trueregression verdict is honored. - Story-loop
ux-adherenceself-heals on the story loop, not only per-AC;architect-revieweraugmentsac.jsonin place (never dropsindependence); the reflection-gate revise brief is additive; migration-reversibility asserts schema-recreation, not data-survival. - Terminal
doneturn is now recorded; after merge the working tree lands on the parent tier and the merged feature branch is removed;prepare-pr --forcegets past the produced.consortcorpus dirty tree. - Hardcoded
.sftdd/path literals removed from source (derived fromARTIFACT_ROOT/cycleDir()instead), a brokendist/bin/sftdd/…reference incapture-scenario.shcorrected,watch-artifacts.shresolves.consortwith a legacy fallback, and.gitignore.baseignores.consort/per-run files.
- Story independence is now enforced at each story's own spec gate. A story
after the first that omitted its
independencedetermination used to pass every per-story spec gate and only fail at the full-feature ship gate, tens of turns and a full build later. The per-story approve path (approveStoryGate) now runs a story-scoped independence check, so the gap fails at the story's own gate. The spec-author also self-checks it at breakdown (a new breakdown mode of the response formatter), and the breakdown prompt + task hint demand the determination up front, so it is caught before the gate at all. - The build fails fast on expired Databricks auth instead of hanging. When
the OAuth refresh token had expired,
generate-database-credentialfailed and the app's connection pool hung, so a per-turn verify stalled indefinitely (a cached access token letcurrent-user mepass, masking it). A drive-time auth preflight (driveAuthPreflight) now checksdatabricks auth token --force-refreshbefore the run, and an auth-expired verify failure escalates immediately (sourceauth-expired) rather than routing a self-heal detour, so the human sees a clear "re-authenticate" message in seconds, not a spin. (The runtime-token app template also raises a typedDatabricksAuthExpiredinstead of hanging the pool.) Pairs with@databricks-solutions/lakebase-scm-utilsv0.1.0, which the kit now pins. - Reactive-green supersession turns are labeled so replay stays aligned. When
a story's honest-GREEN verify failed because it superseded a prior test, the
Navigator assessed it and the Driver permissively re-greened , but that
re-green turn recorded with no build-mode suffix, so a build-turn replay saw a
spurious extra
green([red,green,green,review]) it could not drop. That turn now routes with a distinctgreen-supersededbuild mode (running the identical honest GREEN; only the recorded label changes), and both the replay filter and the corpus-integrity guard drop it like the other verify-failure detours.
- A re-recorded
stockflow-rerecordscenario corpus captured end-to-end on the current kit (design + build lanes both live, including the DBA design-lane role and a fully live planning lane). Two sprints on one project, both shipped end-to-end (build + deploy + promote/merge to staging): F1 record-stock visibility and F6 the split-tracking-code expand/contract migration. It is the standing replay-scenario regression corpus.
lakebase-update-agentsrefreshes a scaffolded project's.claude/agents/role definitions from the current kit. Project creation only seeds agents (it never overwrites one already present), so a kit bugfix to a role prompt did not reach an already-scaffolded project; this closes that gap, and the drive additionally auto-refreshes agents when it detects the kit version moved (a.claude/agents/.kit-versionmarker).--dry-runpreviews;--keep-localpreserves a project-edited agent.- Recorded turns now carry an agent transcript (
transcript.md: the task prompt, the tools used in order, and the turn's final reasoning), summarized inturn.jsonand flagged in the turns index. Purely additive (replay and corpus integrity ignore it); it gives a demo/visualization the "what each role was asked, did, and decided" material the artifact delta alone does not carry.
- The plugin manifests track the release version.
.claude-plugin/plugin.json(and.cursor-plugin/plugin.json) were frozen at0.1.0, soclaude plugin updatecompared versions, saw no change, and refused to refresh the cache even when content moved. They now matchpackage.json(aplugin.test.tsguard fails on drift), so the built-in updater refreshes on every release. - Documented updating an existing project:
./scripts/lk --warm(the canonical "pull the latest kit" step),lakebase-update-agents,lakebase-update-commands, andclaude plugin update, in the README and the/consort:startresume path. - The abort message for a role whose artifact failed its contract no longer says "returned nothing" when the artifact is present but non-conformant.
- Tests own their database state (no shared-state contamination). The test-strategist now scopes each test's seed and assertion to its own rows (or asserts a delta) instead of an absolute whole-table aggregate, so a story's tests no longer flake once another story's rows share the table. As a safety net, the deploy-verify contamination self-heal (classify by isolation re-run, Navigator assess, Driver scope, one honest re-verify) now also covers the feature-ship deploy, not just the per-story deploy.
- The planning lane records the committed feature's size on a re-plan sprint.
A per-sprint
estimate-committedturn sizes the committed feature and re-syncs the sprint backlog, sobacklog.jsoncarries per-sprint sizing even when the sprint re-plans rather than proposing fresh. - The green-failure assess turn is decisive when the failure is already localized. When a deterministic advisory has pre-localized the superseded tests, the Navigator's assess prompt tells it to trust that and flag in one call rather than re-scan the whole tree, closing a long assess spin.
- A service no longer implies a database.
service_backedused to force a schema: aservice_backedfeature with zero tables or zeropersistence_invariants[]hard-failed the design gate, so a compute / transform / proxy / external-API-aggregator service (real business logic, no persistence) could not pass.persistence_invariants[]is now the single source of truth for "this feature has a database": declaring>=1routes the feature through the DBA + DB-integration tests; declaring none marks it a non-persisting service (the DBA turn is skipped,db-design.jsonis optional, no persistence coverage is required).checkDbDesignandcheckPersistenceCoveragenow key off the invariants, notservice_backed. The safety net against under-declaring a feature that really persists moves intocheckServiceBackedDeclaration: persistence evidence (anInfra-layer AC, or a migration/schema/storage NFR) whilepersistence_invariantsis empty still hard-blocks the gate. Layering enforcement is unchanged (everyservice_backedfeature still declares boundary/service/repository). Agent prose (dba.md,architect-reviewer.md,test-strategist.md), the architecture schema, and the DBA task hint were realigned, with a regression guard insftdd-agent-defs.test.ts.
- DBA design gate was unsatisfiable for any feature with persistence
invariants. The
dba.mdprose told the DBA to writerealizes_invariants[]entries "mapped to the physical construct that enforces it", which led every model to emit objects ({invariant_id, realized_by}), but the schema, the conformance gate, and the recorded corpus all require a flat array of bare invariant-id strings. The two disagreed, so the DBA looped until the drive aborted with a PROTOCOL VIOLATION. Aligned the prose (and the orchestrator's DBA task hint + remediation) to the schema:realizes_invariants[]is a flat string array (the "which construct realizes it" rationale lives indb-design.md),unique_constraintsis an array of column-name arrays, andcolumn.defaultis a string SQL expression. Added a regression guard (sftdd-agent-defs.test.ts) pinning the DBA prose to the schema. - Misleading abort message. When a role's expected artifact was present but failed schema validation, the drive aborted saying it "returned nothing (the expected artifact is absent / null / empty)". It now says the artifact "did not satisfy its contract (absent, empty, OR present-but-nonconformant)" and appends the remediation, so a shape mismatch is not misreported as a missing file.
- Repin
@databricks-solutions/lakebase-scm-utilstov0.1.0-beta.11. It fixes two first-run create findings at the source:lakebase-create-projectnow accepts a pre-existing EMPTY target directory on the--no-githubpath (only a non-empty dir is refused), and--no-githubwith--tiers 2/3is rejected up front (before any provisioning) instead of provisioning tier 1 and skipping the extra tiers with a post-hoc warning. /consort:startinterview + first-project walkthrough reconciled to that behavior. The project-name guidance now says an empty (or absent) directory is fine on--no-githuband a non-empty one is refused; the tiers guidance says--no-github+--tiers 2/3is refused up front (pair--no-githubwith--tiers 1).
--agent-modelhelp role list corrected. It listedrelease-engineer(which is deterministic, not a tunable agent) and omitteddba. The accepted roles arespec-author, architect-reviewer, dba, test-strategist, ux-designer, navigator, driver, product-owner.
- Environment doctor gate on project creation.
lakebase-create-project(and/consort:start's create path) now runs the doctor before any provisioning and refuses to start when a hard cold-start check fails (a missing tool, or a workspace without Lakebase), printing the fix. Closes the gap where a good diagnostic existed but nothing on the create path ran it, so a bad environment first surfaced as a failure partway through provisioning a repo and a database.--skip-doctorbypasses; the JDK check blocks only java/kotlin. - One-line bootstrap (
bootstrap.sh).curl … | bashdetects each required tool (Node 20+, npm, Python 3.10+, JDK 17+,gh, Databricks CLI), offers to install or upgrade what's missing, then runs the environment doctor. - First-project walkthrough under
examples/first-project/: a copy-ready, from-zero StockFlow example (intake + feature-proposals + feature-requests + the narrated/plan → /design → /build → /deploywalkthrough).
- Repin
@databricks-solutions/lakebase-scm-utilstov0.1.0-beta.10(the extended doctor: cold-start prerequisites + alakebase-enabledworkspace probe). - README "Get started" leads with the bootstrap + doctor step 0 and folds the
prerequisite list into a
<details>block. - Docs de-jargoned (dropped grandiose framing and meta-narration) and internal
design notes relocated under
docs/design/sodocs/reads as user space.
- Repinned the substrate dependency
@databricks-solutions/lakebase-scm-utilstov0.1.0-beta.9, which ships the React client scaffold's.gitignorevia an npm-safe.gitignore.base(npm strips a literal.gitignorefrom a packed tarball). Without it a scaffolded project committedclient/node_modules/, whose Vite/Vitest cache kept the tree perpetually dirty and madescm-prepare-prrefuse to open the promote PR.
- Scaffold the kit's own
.claudeassets. A newly created project received only the substrate's skill, so it had no.claude/agents/and the driver'sclaude --agent <role>spawns resolved nothing (the design lane halted at the first agent turn). The substrate must not name the kit, so the kit's setup hook now lays down its own role agents, skills (consort + the engineering canon), and workflow commands, deduped against the substrate's. - Sprint-mode feature claim. The whole-sprint driver claimed a feature through a
hardcoded kit-dist path to
scm-claim-feature.cli.js, a substrate bin that moved to@databricks-solutions/lakebase-scm-utils, so it crashed withMODULE_NOT_FOUND. It now claims through the project'slkshim (lakebase-scm-claim-feature-branch), matching per-feature mode. - Coupled reflection defects now co-heal. A pre-build reflection that flagged both a spec-level and a test-list defect could not self-heal: the revise re-ran only the routed role and burned independent per-smell budgets, so the sibling defect re-fired and hard-halted the run. A reflection revise now re-runs the whole design lane for the story (staling the architect's product and briefing both the architect and the test strategist), co-heals every open reflect smell for the story, and is budgeted per-story (one re-design, then the human gate), matching the documented intent.
- Test Strategist migration coverage. For a data-carrying migration (additive column,
expand/contract, backfill) with a declared data-durability NFR, the Test Strategist now
emits a data-preservation fitness test (seed rows, run the migration, assert they
survive) alongside the reversibility round-trip; covering only reversibility is a
reflect-testlist-defect. - Transient agent failures no longer hard-halt the drive. A single transient API or
network blip (connection closed mid-response, overloaded, rate-limited, 5xx) used to
kill a whole run, since the driver aborted on any nonzero
claudeexit. It now retries the turn on a bounded budget with exponential backoff, in the same session. An auth failure is deliberately not treated as transient (it needs a human/login), so it still surfaces instead of retrying futilely. - Test Strategist self-check enforces reverse AC coverage. The role's self-check
verified each test item maps to a real AC but not that every AC carries a test, so an
uncovered AC (often a client-presentation AC) slipped through to the reflection gate as
a
reflect-testlist-defect. The self-check now hard-fails on any AC with no covering test, so the gap is fixed within the Test Strategist's own turn instead of downstream. - Test Strategist self-check rejects a mislabelled fitness item. A
kind:"fitness"item carrying a Gherkin.featurescenario_file(mutually exclusive per canon) now fails the self-check in-turn, rather than reaching the reflection gate as areflect-testlist-defect. - NFR coverage is per-feature-relevant, not per-feature-mandatory. The conformance
gate required every feature's architecture to cover every project-wide Required NFR, so a
feature that touches no code a given NFR governs hard-halted the promote gate even when a
sibling feature already realized it. A Required NFR is now satisfied when the feature
covers it, a sibling feature covers it, or the feature explicitly declares it out of scope
(
nfr_out_of_scope). - Refactor-verify failures route to a bounded supersession assess. A REFACTOR-phase verify failure hard-halted to the human gate, unlike the structurally identical GREEN and deploy-verify failures, which route to a bounded Navigator supersession assess. That stranded a legitimate supersession (a later story's refactor retires a field a prior story's test still asserts). The first refactor-verify failure now routes to a Navigator assess (flag the superseded prior tests, then the Driver permissively refactors only those, then one honest re-verify), one-shot bounded; a genuine regression or a repeat failure still takes the terminal human gate. The honest re-verify gates every round, so it never green-washes.
- Reflection revise budget is now progress-based. The reflection critic surfaces test-list defects one at a time, but the prior budget allowed only one re-design, so a story with more than one latent defect hard-halted after the first heal. A reflect revise is now allowed while under a cap and the prior revise actually changed the test-list (fingerprinted at revise time); a no-change re-emit (a stuck author) or the cap still halts, so the loop converges instead of stalling a headless run.
- Renamed to Consort. The package is now
@databricks-solutions/consortand the GitHub repo isdatabricks-solutions/consort(the old URL redirects). The framework skillconsort-workflowsis nowconsort, with a/consort:*plugin command namespace and a Consort MCP identity. Consort is positioned as a Spec-First Branched-Database TDD agent framework built on governance by construction. Internalsftddmodule names, theconsort-*bin names, and the scaffolded.sftdd/runtime dir are intentionally unchanged for back-compat with already-scaffolded projects. - Repinned the substrate dependency
@databricks-solutions/lakebase-scm-utilstov0.1.0-beta.6(apply-tier subcommand for 2-tier/3-tier merge migrations + the lk non-prefixed kit-bin routing fix), then to the scm-utils commit carrying the consort scaffolder.
- DBA design-lane role (the ninth role). A
dbaagent runs between the architect reviewer and the test strategist: it consumesarchitecture.jsonand produces a feature-scopeddb-design.json(physical tables/DDL + per-story schema-change plan) that realizes the architect's persistence invariants (the architect keeps ownership of the invariants; the DBA realizes, never re-authors). The spec gate hard-blocks a service-backed feature whosedb-design.jsonis missing or leaves any declared invariant unrealized.
- Purged the dead per-language app-scaffold template trees under
templates/project/(client/python/java/kotlin/spring, nodejs except its.gitignore.extra, and the deadcommon/playwright/e2e fixtures) that the scm-utils extraction orphaned; scaffolding runs off scm-utils's own bundled templates. The livecommon/**BDD fixtures are kept.
- Track C Phase 2 + 3: the SCM + shared substrate now lives in the standalone
@databricks-solutions/lakebase-scm-utilspackage, which the kit consumes as a dependency (v0.1.0-beta.3). The kit keeps the SFTDD orchestration, the MCP server, and the SFTDD-flavored project scaffolders. - Decoupled
createProject/adoptLakebaseProject: the base scaffolders moved to the substrate package (SFTDD-agnostic); the kit wraps them and injects the.sftdd/lay-down + sftdd-config seeding viascripts/sftdd/project-sftdd-setup.ts. The kit'slakebase-create-projectCLI still produces SFTDD-ready projects; a plain SCM consumer (the VS Code extension) uses the base scaffolders directly. - The root
.barrel re-exports the substrate package, so createProject / adoptLakebaseProject / assertAdoptionPreflight remain reachable from the kit's public API.
- De-coupled the SCM core from SFTDD, ahead of extracting
@databricks-solutions/lakebase-scm-utils(FEIP-8263, Phase 0).scm-doctor.tsno longer importsscripts/sftdd/*(the only reverse edge in the SCM/substrate core): the stale experiment/spike finder is now an injectedRunDoctorDepsdependency (StaleBranchFindingdeclared locally), andscm-doctor.cli.tswires the SFTDD finder as the single accepted composition point. Thelakebase-scm-doctorbin behaves identically (no test relied onrunDoctorauto-finding stale branches). The dirty-tree ignore list (.sftdd/.tdd/.lakebase/.claude/agent-memory/) is centralized intoRUNTIME_ARTIFACT_IGNOREinconstants.tsand referenced frompaired-branch.ts+scm-prepare-pr.ts, so no SFTDD-dir literals remain scattered in SCM core. Internal refactor; no user-facing behavior change.
- Split-readiness guard test.
scm-core-no-sftdd-import.test.tsfails if any module underscripts/{lakebase,github,git,util}importsscripts/sftdd/outside an explicit allowlist (the project scaffolders + thescm-doctorCLI wiring), and assertsscm-doctor.tsstays SFTDD-free. Enforces the one-way dependency direction for the pending package split.
Documentation audit remediation (FEIP-8085), plus one CLI help-text fix. Docs match the code at this version.
lakebase-branch --helpno longer advertises the deletedcreate-tiersubcommand. The unpairedcreate-tierwas removed, but the help text (usage line, flags section, example) and two code comments still listed it; repointed to the shippedcreate-paired-tier. Help-text and comments only, no logic change.- Documentation brought current with the shipped code (FEIP-8085). A four-agent audit corrected two classes of staleness: completed items still labeled proposal/future (eight
docs/refactor/*proposals whose work shipped, five scaffolded command docs calling the shippedlakebase-update-commandsbin "future", the SCM SKILL's shipped "future work" and stalealpha.45stamps), and instruction drift from the code. The largest drift fixes are in theconsort-workflowsskill (read by agents at runtime): the experiment APIs are now documented story-scoped (astoryIdthreaded through the primitives + theexperiments/<feature>/<story>/<slug>/path), the 5thdeploygate is in every gate list, and the model-resolution source, roles table, entry points,feature-status-schemaexample, andCONFIGclientFrameworkrow are corrected. SCM/root docs dropped a phantomlakebase-get-connection --write-envflag, fixedcreate-tier->create-paired-tierin the root README,migrate-live*->schema-migrate-live*in CONTRIBUTING, and added the.lakebase/kit-ref.localrun pin to the capture runbook's kit-ref resolution order.
- Anti-recurrence test:
SKILL.mdmust name every gate inGATE_NAMES. The SFTDD skill's gate documentation had drifted behind the code (thedeploygate was added but the named-gate list was not); the test fails if a future gate is added without updating the doc.
Three field findings from the F4-pick-outbound feature of a live stockflow tier-2 run (FEIP-8070).
- The drive pins the run's kit-ref to a checkout-proof
.lakebase/kit-ref.local(Finding 28, High)..lakebase/kit-refis git-tracked, so the drive's branch operations (claim checkout, experiment cut/re-fork, which fork fromorigin/<parent>) silently restored a branch-committed kit-ref over an operator's working-tree bump, running the WRONG kit version mid-run with no signal (an entire F4 story built on the stale backend;assertCleanForForkeven ignores.lakebase/, so the bump was clobbered without blocking the fork). The drive now resolves the launch ref once at startup and writes it to a gitignored.lakebase/kit-ref.local, which survives checkouts (git never touches untracked files); thelkshim reads.localwith precedence over the committed.lakebase/kit-ref(which stays tracked, CI resolves itsKIT_REFfrom it), so the whole run (orchestrator, subagents, and manuallkcalls) keeps the launch ref. The drive warns loudly on drift between the committed and pinned ref. Newkit-ref.tsservice (resolveLaunchKitRef/pinRunKitRef/kitRefDriftWarning);run-configrecords the effective ref. - The per-story spec gate refuses a truncated / invalid-JSON AC at approve time (Finding 29, Medium). A spec-author could write a truncated AC file (ending right after
architectural_noteswith no closing brace); the spec gate and reflect gate never JSON-parsed the AC files, so it passed both and only failed at deploygate-conformance("not valid JSON"), long after build + accept.approveStoryGateFromDisknow runs the SAME per-story conformance the deploy gate trusts (newstoryAcsConformanceReason, reusingcheckArtifactConformance), so a malformed AC is refused where it is produced.
lakebase-scm-doctorgains ascm-state-git-trackedwarning (Finding 28 sibling)..lakebase/workflow-state.json(the per-working-tree SCM claim state) is git-tracked, so a branch checkout orgit reset --hard origin/<tier>can restore a stale committed claim over the live one; the finding flags this so a wrong-claim refusal after a checkout is understood as this, not a real conflict.
- Migration-reversibility tests must seed idempotently (Finding 30, Medium). The agent-generated migration-reversibility test seeded a fixed-key row with a plain
INSERT+finallycleanup; a run killed mid-test (the runtime caps long drives) commits the seed but never cleans up, so every later run on the long-lived reused branch DB failed on a duplicate-keyUniqueViolationunrelated to the code under test. The guidance intest-strategist.md+navigator.mdnow requires an idempotent seed at the START (a per-run-unique key, orDELETE-before-INSERT/INSERT ... ON CONFLICT DO NOTHING) in addition to thefinallycleanup.
- A re-driven story now re-runs RED/GREEN instead of reusing its stale (false-GREEN) build (FEIP-8052, Finding 27).
discard/pipeline revise/set --status buildingreset the pipeline status but left the per-story cycle records under.sftdd/cycles/intact. The drive derives "build already GREEN" (codeWritten→storyTestProgress.allGreen) from those stalegreen_atfiles, so a story sent back to be rebuilt skipped the RED/GREEN cycle and jumped straight to deploy, re-failing the same deploy-verify in a loop; recovery required hand-deleting kit-internal cycle records + the paired Lakebase experiment branch. Root cause: split-brain,resetStoryBuildState(the exact clear-cycles primitive) existed but was wired into only theconsort-experiment discard --revisedoor. Now both revise doors (applyReviseSelfHeal+ the plain reset) call it, so the operatorpipeline reviseand the driver's auto revise-route re-drive from a clean slate.
consort-pipeline rebuild-story(Finding 27). The sanctioned "re-drive this story from a clean slate" op, replacing therm -rf .sftdd/cycles/...recovery dance. It clears the build cycles + test-list, BOTH HIL escalation sources (escalation files and blocking smells, the dual-source rule), marks the experiment for a clean re-fork, and puts the story back on the single build lane (refuses when the lane is busy with a different story).consort-experiment cut --reset-stale-branch(Finding 27). Drops a pre-existing paired branch of the same deterministic name before forking, so a re-cut after a discarded experiment re-forks clean off feature HEAD instead of reusing the branch that still carries the discarded build's schema (mirroring the ci-pr--reset-stale-branchprecedent). The drive auto-passes it on a re-cut after a discarded experiment. The drop-then-fork ordering is hermetically tested via an injected paired-branch ops seam; the live DROP + re-fork against a real Lakebase branch is validated by a live drive.
- The build's honest-GREEN verify now runs the client (Vitest) suite, not just the backend (FEIP-8051, Finding 26). The build lane could mark a story GREEN while the client Vitest suite failed deterministically; only the later deploy feature-verify caught it, a violation of the core TDD guarantee. Root cause: the build honest-GREEN runs the Python backend via the
SFTDD_PYTEST_MARKERtwo-pass, andrun-tests.sh's marked branch exited BEFORE its client Vitest block, so the client suite never ran under build GREEN (the deploy gate runsrun-tests.shunmarked, reaches the client block, and failed, exposing the drift). Python + client scaffolds only. Nowrun-tests.shgainsSFTDD_CLIENT_ONLY=1(skip the backend, run only the client Vitest block), andensureDeployedAndVerifyruns one client-only pass after the two marked backend passes (when aclient/workspace exists), gating GREEN on its captured exit code, so build honest-GREEN gates on the SAME client tests the deploy gate runs. The marked passes stay backend-only (no double-run, migration isolation intact). - The scaffold's client API layer teaches the RFC 9457 Problem Details field-error pattern (Finding 26, secondary).
client/src/api/client.tspreviously surfaced a validationdetailonly when it was a string, so an object-shapeddetail: { field, message }refusal was dropped and thrown as a generic error, never reaching a form'sfield-<field>-errorseam. It now parses both shapes via_problemDetail, carries the field onApiError, and adds apostJsonmutation helper that throws it, so the driver has a reference for the object-detail + field-seam pattern.
Handover findings batch (FEIP-8050): six field findings from a stockflow tier-2 run. Cross-cutting theme: a branch's DB is addressed by its {instance, branch} DSN, never LAKEBASE_BRANCH_ID / ambient LAKEBASE_HOST.
-
lakebase-feature-statusno longer shows a shipped feature's deploy/promote asopen(Finding 13). It dumped the rawgates.jsonapproval bit, so a feature the drive had already deployed + merged disagreed withconsort-next(which reconciles fromdeploy-evidence.json+ the SCM workflow-state). feature-status now consumes the SAME reconciliation (readDriveContext) via a newprogressionsnapshot field and rendersdone (deployed)/done (merged). The CLI gains--project-dirto reach.lakebase/. -
CI workflows follow
.lakebase/kit-refinstead of a baked version pin (Finding 24). Scaffoldedpr.yml/merge.ymlhardcoded the scaffold-time kit version at every kit call site, so bumping.lakebase/kit-ref(which the runtime substrate follows) never reached CI, every run executed the stale kit. Both workflows now resolveKIT_REFfrom.lakebase/kit-ref(fallback = the scaffold version) and use#"${KIT_REF}"at all call sites.lakebase-scm-doctorgains aci-workflow-kit-pinwarning that flags workflows still carrying the literal pin. -
The
/designreflect gate andpipeline revisenow converge instead of looping (Findings 22 + 23). The operator-facingconsort-pipeline revisewas hollow: it reset the story but never cleared the blocking smell (so the next drive re-raised it at action 000) and never re-briefed the test-strategist (so the regenerated test block re-omitted the requested coverage, forever).revisenow runs the same self-heal the driver's auto-route uses: it resolves the smell, writes a coverage-forcing hand-back brief so the test-strategist MUST add the named test, and spends the one-revise budget so a re-fire hard-halts to the human.discardclears a discarded story's blocking smell; a newresolve-smellsubcommand replaces hand-editingsmells.json. -
The promote local-migrate fallback verifies the target before reporting in-sync (Finding 25). It reported "git and Lakebase schema are in sync" purely from the apply exit code, a lie when the apply no-ops against the wrong branch or applies partially.
applyAndVerifyTierMigrationnow reads the TARGET branch's status back (pending == 0) after applying; when it is not verified at head it reportsmigrate-unconfirmed, which BLOCKS workflow completion instead of printing a false in-sync. -
Shared tier reconcile +
ci-prreset (Finding 21). Two gaps beyond the beta.27/.28 feature-branch recovery:- CI reused
ci-pr-<N>across runs, so a branch first cut while its source tier was polluted rode that pollution through even after the tier was reconciled.lakebase-ci-resolve-branch --reset-on-db-ahead(wired inpr.yml) probes a reused, source-verifiedci-prfor db-ahead-of-code and re-forks it from the now-clean source. - There was no path to reconcile a shared TIER branch (you cannot delete + recreate staging/prod). New
lakebase-reconcile-tierreconciles a tier whose DB is ahead of code: it refuses on a healthy tier, drops the named orphan tables (identifier-safe), and stamps the branch to the code head. Backed by a newstampSchemaMigrationprimitive + an Alembic adapterstamp(alembic stamp --purge, which clears a phantomalembic_version).
The live-DB halves (Finding 25's target verify, Finding 21's tier stamp + table drop, and the
ci-prreset) are validated by a live drive; their decision + orchestration logic is hermetically tested via injected seams. - CI reused
- The pre-push hook now provisions a DURABLE CI credential instead of a ~1h OAuth token (FEIP-8020). CI reruns and the downstream (staging) migrate authenticate with
DATABRICKS_TOKENfrom GitHub secrets, butpre-push.shsynced a short-lived (~1h) OAuth session token (databricks auth token) frozen at push time, so a rerun or migrate firing more than an hour after the push failed auth (partial promotion). Thecreate-token-and-sync-secrets.shscript (mints a 90-day PAT, OAuth fallback only where PATs are disabled) already shipped but nothing invoked it.pre-push.shnow delegates to it, wrapped so a mint/sync failure WARNS rather than blocking the push. A 90-day token re-minted on every push outlives any realistic rerun / migrate window. (The full OAuth service-principal M2M , client-credentials, no long-lived secret at rest , remains a documented future hardening.)
- A feature claim refuses (and can reset) a paired branch whose DB is ahead of code (FEIP-8039 recover). Completes the beta.27 fix (which prevented a build from migrating a tier + added a
db-ahead-of-codedoctor check). Feature Lakebase branches are non-expiring, so a re-claim REUSES an existing branch as-is; if an earlier aborted build migrated it and agit resetremoved the migration file, the reused branch carries a phantom alembic revision + orphan table and later fails "Can't locate revision". Now:lakebase-scm-claim-feature-branchruns a live DB-ahead-of-code probe on the cut/reused branch and refuses (db-ahead-of-code) rather than silently adopting a polluted branch;--reset-stale-branchdrops it and re-forks clean from the tier instead.lakebase-scm-doctor --fix db-ahead-of-coderesets an already-claimed stuck project (delegates to abandon: deletes the polluted branch + resets state toscaffold-complete), so a re-claim re-forks clean.- New
branchRevisionOrphanprobe wraps the beta.27 detectors. The live probe + branch delete are validated by a smoke; the refuse/reset/fix decision logic is hermetically tested.
- Design subagents no longer strand artifacts at a malformed project root, and a stray tree self-heals (FEIP-8038). The story-scoped design roles (spec-author draft, architect-reviewer) were handed RELATIVE artifact paths, so (per FEIP-8006, the Write tool needs an absolute path) each resolved the project root itself and sometimes malformed it , the parent workspace dir and the project dir joined with a hyphen instead of a slash , writing every artifact outside the real project; the out-of-root guard then bailed and the "re-run" remedy looped forever. Two parts: (a) every design directive now names the ABSOLUTE artifact root for its write targets, so no role resolves the root itself; (b) the out-of-root guard now relocates a stray
.sftdd/.tddtree from the one known malformed sibling (<parent>-<project>) back into the real root and re-checks, so the run self-heals instead of deadlocking (and names the sibling in the error otherwise). Bounded to that sibling pattern, no filesystem scan. - A build/experiment migration refuses a protected-tier target, and scm-doctor detects a DB ahead of code (FEIP-8039). An aborted build ran
alembic upgradeagainst its paired Lakebase branch; a latergit reset --hardrolled back only git, and because feature branches are non-expiring andcreateBranchis idempotent-on-existing, the re-cut feature reused that same stale branch, so its DB carried a phantomalembic_version+ an orphan table and accept/deploy/promote failed "Can't locate revision".applySchemaMigrationsnow refuses a protected-tier target branch (main/master/staging/dev + configured tiers) before any DB work; the promote path (scm-mergelocal fallback) opts in viaallowTier, since it migrates the parent tier by design. Newlakebase-scm-doctorcheckdb-ahead-of-codeflags a paired branch whose applied revision has no local migration file. (The recover , resetting the polluted branch so the next claim re-forks clean , lands separately with a live smoke.)
- The /design breakdown no longer deadlocks when the spec-author writes the
story stubs but not feature-spec.json (FEIP-8024). The driver gates the
breakdown on
feature-spec.json(a non-emptystories[]), but the spec-author prompt said the breakdown deliverable was ONLY the story stubs, and the breakdown directive named neitherfeature-spec.jsonnor the absolute artifact root. So the agent wrote just the stubs, the drive failed "produced no feature-spec.json", and on re-run the agent saw its stubs already present, reported the breakdown done, wrote nothing, and failed the identical guard forever (the printed "re-run" remedy looped). Three-part fix:- Aligned the breakdown contract. The breakdown directive now requires
writing
feature-spec.json(id/name/status/tdd_mode + a non-emptystories[]) plus the story stubs, and interpolates the ABSOLUTE artifact root (mirroring the propose directive). Naming the root also ends the malformed project-root guess the subagent made in breakdown mode. - A deterministic deadlock-breaker. Before every breakdown turn the driver
resets an INCOMPLETE breakdown (story stubs present but
feature-spec.jsonabsent or empty-stories), so a re-dispatch always regenerates from a clean slate regardless of the agent's idempotency behavior. A no-op on a complete breakdown or a clean slate. Newconsort-pipeline reset-breakdown. - Spec-author prompt. The breakdown deliverable is now
feature-spec.json(the feature index) + the stubs, with idempotency keyed onfeature-spec.json.
- Aligned the breakdown contract. The breakdown directive now requires
writing
- A Tier-2 drive of a fresh feature no longer commits build output onto the
shared tier when a prior feature shipped out-of-band (FEIP-8023). With a
predecessor feature promoted outside the drive and
.lakebase/workflow-state.jsonnever reconciled, driving the NEXT feature cut no feature branch and committed the GREEN build straight ontostaging:buildCfgadopted the stale predecessor's branch as this feature'sfeatureBranch, so the experiment forked from (and the build committed onto) the wrong branch, bypassing the experiment to feature to promote-PR flow entirely. Two hard-block guards, defense in depth:- Foreign-claim refusal.
consort-drive --feature <F>now refuses loud (names both features + the remedy, exits non-zero) when the recorded SCM claim names a DIFFERENT feature, before the driver runs, killing the stale experiment-parent derivation at the source. Claim this feature (or reconcile the prior out-of-band one) first. - Protected-branch commit guard. The build lane's commit path
(
commitExperimentCode) now refuses to commit onto a protected tier (main/master/staging/devplus anyLAKEBASE_TIER_NAMES/ configured trunk/staging/base names), and the per-cycle commit re-throws that refusal instead of swallowing it. Even with stale bookkeeping, a build commit aimed at a shared branch now fails loud rather than silently polluting it.
- Foreign-claim refusal.
- An orchestrator operating contract so the driving agent drives to completed
software instead of narrating (FEIP-8021). The kit governed the ROLE agents
(
agent-operating-rules.md) but had no contract for the agent DRIVING/sprint/design/build/deploy, whose default was to narrate every step and ask the human at each one.references/orchestrator-contract.md(loaded by the four command templates, the same way role prompts loadagent-operating-rules.md) makes the default: drive to completion viaconsort-next, stop for the human ONLY at a HITL gate or a blocker, present the decision (option titles +hil_prompt) not the CLIs run, report outcomes not process, show working software at the acceptance + deploy gates; verbose/eval narration is explicit opt-in (LAKEBASE_SFTDD_VERBOSE=1), off by default. Pairs with FEIP-8017.
- The Tier-1 sprint drive no longer re-enters shipped features or leaks the
coarse feature phase across features (FEIP-8022). Two coupled defects: (1)
the coarse
phasein.sftdd/workflow-state.jsonis per-PROJECT and was honored for any feature, so a prior feature's phase leaked into the next (consort-next --feature F2reported F2 at "deploy" with only a feature-request). Now the phase is stamped with its owning feature and honored only for that feature (planning is sprint-global + exempt); an un-owned/foreign phase falls back to "feature" so the drive ANDconsort-nextre-derive from the feature's own artifacts. (2) the sprint loop re-claimed + re-drove a completed feature; it now SKIPS a backlog feature whose own workflow derives todone. A feature shipped fully out-of-band (promotion merged outside the drive) is recovered by the forthcoming reconcile (FEIP-8018).
- The surfaced promote-gate approval command now includes the required
--promote-ref(FEIP-8019). At the promote gate, both the drive's stop message andconsort-nextprintedconsort-approve-gate --feature <F> --gate promote --approver <you>with no--promote-ref. The promote gate requires a non-emptypromote_ref; running the surfaced command returned "skipped promote (no promote_ref supplied)", a silent no-op, and the drive re-surfaced the same gate. The drive's own internal approval already supplied it (cfg.featureBranch ?? feature); only the human-facing command omitted it. The single structuredgateEnactCommandmap now emits the promote enact with--promote-ref <feature-branch>(read from the SCM workflow state), and bothapproveHint(the drive hint) andconsort-nextproject from it, so following the surfaced command records the approval.
lakebase-scm-mergeinterim mitigation for the short-lived-CI-token migrate failure (FEIP-8020). The promote merge's downstream (staging) migrate authenticates with aDATABRICKS_TOKENsecret frozen at push time; it can expire before the run, so git promotes (PR merged, code on staging) but the parent Lakebase schema migration never applies, a partial promotion. Two mitigations (the durable service-principal M2M credential fix is FEIP-8020's deferred main scope): (1) a migrate-auth precondition run before the merge (when waiting on the migrate) that verifies migrations can be applied and fails fast (migrate-auth) rather than promoting git without the schema; (2) a local-migrate fallback that, when the downstream migrate does not confirm (a failed conclusion or a fatal timeout), applies the parent migrations locally with a freshly-minted token so git and Lakebase schema stay in sync (migrate.appliedLocally). Flags--no-verify-migrate-auth/--no-local-migrate-fallbackopt out.
- The kit's tag now ships a current, complete
dist/for these fixes (the consumer install runs the committed dist;prepare.mjsskips the build for non-dev installs), correcting a stale-dist gap in beta.22 where FEIP-8016 / FEIP-8017's changed bundles had been reverted after the release build.
consort-next: an authoritative, strictly read-only "what do I do next?" surface (FEIP-8017). The deterministic drive knows exactly where the workflow is and what it would do next, but only WHILE it runs; every time it stops (a HITL gate, a raised escalation, feature-complete, an error, a killed run) an orchestrating agent otherwise reverse-engineers the next move from source and drifts into freeform (improvised CLIs, manual git, manual state edits).consort-next (--feature <F> | --sprint <S>) [--json]answers, from the SAME engine the drive uses (deriveDriveState->nextTransition, so it can never drift): the reconciled state (coarse + pipeline-derived phase, per-story statuses, open gates, blockers), the decision MENU (not just the one next action, but the real HIL choices, e.g. accept/discard/revise at acceptance, each with its correct enact command + a prompt to pose to the human), and a truthful summary. It is strictly read-only: no model spawn, no writes to workflow artifacts, no actions. The drive also auto-emits the feature snapshot to.sftdd/next.jsonon every stop (skipped under replay/record), so an agent's contract becomes "on any stop, read next.json and present its options." The gate -> CLI mapping is now a single structured source (gateEnactCommand) that the drive's stdout hint projects from, so the hint and the menu can never diverge. Seereferences/next-schema.md.
consort-feature-statusnow reflects per-story-driven completion (FEIP-8016). A fully built + accepted feature rendered asPhase: discoverywith its feature-level gates stillopen, because the coarseworkflow-state.jsonphase is not advanced per story and so lags behind the per-storypipeline.json(the source of truth). The snapshot gainsderived_phase(DERIVED from the pipeline:completewhen every story is done- accepted,
buildwhen a story is past its spec gate,designotherwise,nullwhen no stories are tracked) and astories[]array of per-story rows; the renderer prefersderived_phaseand annotates the coarse phase only when it lags. A bounded deploy drive over an already-deployed feature now readsalready complete (0 actions, nothing to do; the per-story pipeline already carried it out)instead of the misleadingdeploy complete in 0 actions.derived_phase+storiesare append-only additions to the feature-status snapshot's public shape.
- accepted,
- The acceptance gate now LANDS the accepted story's code, not just its state
(FEIP-8013). The drive's
acceptwas two commands (consort-experiment merge+consort-pipeline accept); the git-merge lived only in the experiment CLI andpipeline acceptrecorded state. Interactive, the gate stops before the accept effect, so a human running the hintedpipeline acceptrecordeddonebut never merged, and the accepted story's code stayed on the experiment branch (the next story then forked from a feature branch missing it). Now runningconsort-pipeline acceptLANDS the code: it RESOLVES the merge args (experiment slug + branches from the persisted experiment record; instance from--instanceelse scm-state) and DELEGATES toconsort-experiment merge, the single CLI that owns the git-merge (+ migrations + teardown) and records acceptance.pipeline acceptitself never touches the merge substrate in-process, it routes through that CLI (as does the drive's singleaccepteffect). The merge is idempotent: a re-run whose experiment is already merged skips the merge and just ensures the acceptance state. Cut and merge agree by construction (accept reads the branchescutpersisted).
-
consort-approve-gateis now the one human door for the per-story spec gate too (FEIP-8008). At a per-story spec gate the drive printed a generic hint (consort-approve-gate --feature <id> [--gate <name>]), but that records the FEATURE-levelgates.jsongate, not the PER-STORY gate the design lane blocks on (managed bypipeline.json, approved byconsort-pipeline approve-gate --story). Following it recorded the wrong gate, exited 0, and the drive never advanced.consort-approve-gatenow accepts--feature <id> --story <s>and routes the per-story gate through a shared helper (approveStoryGateFromDisk) thatconsort-pipeline approve-gatealso uses, so both write identical state. The drive'sGATEmessage now prints the EXACT command per gate kind: per-story spec →--feature --story, plan →--sprint, deploy/promote →--feature --gate <name>, PO acceptance →consort-pipeline accept. -
Scaffolded React client vitest collects its own
tests/component-test layout (FEIP-8009).templates/project/client/vite.config.tssettest.includeto["src/**/*.test.{ts,tsx}"]only, yet the scaffold shipsclient/tests/pages/(where the design lane routes client component tests) as the Vitest home. Any client component test placed there was silently uncollected, so a client RED test could not run and the build escalated with a blockingscaffold-defect("no runner for the layer").includeis now["src/**/*.test.{ts,tsx}", "tests/**/*.test.{ts,tsx}"]withexclude=[...configDefaults.exclude, "tests/e2e/**"], so component tests undertests/are collected out of the box while Playwright'stests/e2e/stays Playwright's. -
The Driver agent's own log lines are now visible in the default agent-log view (FEIP-8010). The Driver role doc told it to emit its narration at
--level debug, while the Navigator emitsreasoningatinfoand the standard log view (and the drive's own tail) read--min-level info, so the Driver's self-narration was filtered out and the Driver appeared to log nothing. The Driver role doc now emitsreasoningatinfoonce per GREEN/REFACTOR turn, at parity with the Navigator.
-
A design subagent writing its artifact OUTSIDE the project root no longer causes a cryptic, misattributed crash (FEIP-8006). A role subagent (seen with the Test Strategist) wrote its output to a hallucinated path outside
<project>/.sftdd/, and a downstream consuming effect then crashed reading the absent file, blaming the wrong step. Two layers close it. (1) Root cause: role prompts now name ABSOLUTE artifact paths under the resolvedsftddDir(the directive root was a bare basename, a relative path the Claude CodeWritetool cannot use, so the agent guessed the project root). (2) Guard: after each design/planning role turn the orchestrator emits averify-artifactcheck that asserts the role's expected output landed under the projectsftddDirBEFORE any consuming effect runs; on a miss the driver throwsArtifactOutOfRootError, a loud, attributed failure naming the role, the artifact, and where it looked, with the hint that the agent likely resolved the root wrong. Build turns (navigator/driver) and the human-input author-requests step are exempt. -
The pre-build reflection gate now CONVERGES instead of looping the Navigator to the stall guard (FEIP-8007). When the reflect gate correctly flagged a design defect (e.g. a
reflect-testlist-defect), the run could re-dispatch the Navigator reflect turn repeatedly and exit with "driver stalled ... repeated without advancing state", because the defect lived in the Test Strategist's artifact and re-running the Navigator could never fix it. Two root-cause gaps are closed so the existing revise-route machinery converges. (1) The revise self-heal now INVALIDATES the stalereflect-verdict.json(clearReflectVerdict), so after the owning author re-authors, the re-dispatched Navigator recomputes fresh against the corrected artifact instead of reusing the pre-fixpassed:falseverdict. (2)recordReflectionGateis now idempotent (records an owner's smell only when one is not already open, via a sharedhasOpenSmellguard) and self-clearing (a passing verdict drains the open reflect smell(s) for the story viaresolveOpenSmells, with a newclearedresolution kind that does NOT spend the one-revise budget). Net behavior: a flagged defect routes to the producing role for one informed retry, then a fresh recompute passes (proceed) or the spent budget escalates to a clean HITL pause. The Navigator is never re-dispatched against an unchanged artifact, and the smell log never accumulates duplicate open entries.
consort-approve-gate, a human-facing gate-approval CLI (FEIP-8005). The interactive plan gate (and per-feature HITL gates) await a human's approval, but the only CLI that RECORDED an approval wasconsort-human-proxy, which is explicitly labeled "NOT for production use" and defaults the approver to "human-proxy". A real Product Owner approving a real gate had to reach for a not-for-production tool. The newconsort-approve-gateis the production counterpart: it REQUIRES an explicit--approver(no silent default identity, the deciding human names themselves) and reuses the SAME approval substrate (approveSprintPlanGatefor the sprint plan gate;drainGatesAsHumanProxy, which assembles each open gate's artifact hashes and callsapproveGate, for a feature's gates), so the recorded approval is byte-for-byte what the workflow expects. Usage:consort-approve-gate --sprint <s> --approver <you>(plan gate) or--feature <id> --approver <you> [--gate <name>](a feature's gates). The tool records ATTRIBUTION; the decision must be the approver's. The/plandoc and the driver'sGATEmessage now point humans at it; the Human Proxy remains the headless / smoke path.
consort-sync-backlog, the human door to commit an interactive sprint backlog (FEIP-8002). Interactive sprint planning deadlocked atauthor-requests:backlog.json(from whichrequestsAuthoredis derived) is written only by theauthor-requestseffect (supply-requests+sync-backlog), which the interactive driver stops BEFORE performing;supply-requestsreads sprint membership only from the proxy env channel; and there was no standalone sync-backlog CLI. So a human-in-the-loop Product Owner could authorfeature-request.mdfiles but never commit a backlog or reach the plan gate. The newconsort-sync-backlog --sprint <s> [--features F1,F2]declares this sprint's membership tosprints/<s>/requested.json(the SAME one file the Human Proxy writes, via new sharedreadRequested/writeRequestedhelpers , one membership source, no contradictory door) and projectsbacklog.jsonfrom the requested features that have afeature-request.md. The interactive loop is now: driver pauses atauthor-requests-> PO authors requests + runssync-backlog-> re-run advances to the (interactive) plan gate. Theauthor-requestsPAUSE message names the CLI;/plandocuments the step. Headless (Human Proxy) is unchanged , itssupply-requestsperforms the same projection automatically.
- Interactive
--plan-onlyno longer misreports a PO pause as an approved plan (FEIP-8001). In interactive mode (the default),consort-drive --sprint <s> --plan-onlycorrectly stops after the Architect's estimate at the Product Owner'sauthor-requests(the human must write the feature-request(s)). But that stop is a human-INPUT action, not an approval gate, and the completion handlers only inspected the approval-gate stop , so the run printedplanning complete (plan gate approved)and exited 0 despite producing nothing (nobacklog.json/gates.json/feature-request.md, workflow-state still atdiscovery). A caller would advance on an empty backlog. The human-input stop is now carried distinctly (pendingInput):runSprinthalts on it instead of falling through to an empty backlog, and the CLI reports a clearPAUSED , the PO must author feature-request(s), then re-run. Nothing was approved or producedand exits NON-ZERO in the--plan-only, sprint, and--featurebound paths (the postcondition , an approved plan , is not met). A genuine approval gate still exits 0 (work produced, awaiting approval).
Hardening from field feedback against beta.14, plus a consumer-facing packaging fix. The through-line: a replay is a RECORDING. It now fails loud on a missing corpus artifact instead of silently spawning a live agent, the shipped scenario corpus is guarded complete, and the declared gate policy is human-in-the-loop by default. Validated by a full live stockflow F1+F6 replay (design through promote, zero agent-takeovers) on top of the hermetic suite.
- Replay-corpus completeness guard. The scenario-corpus integrity test was
broadened from "every test-list
ac_idresolves to a tracked ac file" to the FULL set of artifacts the driver restores on replay: per-featurefeature-spec.json/architecture.json/test-list.json, per-storystory.json/reflect-verdict.json/ at least one ac, the uiTrackdesign-guide.json, and the per-storyrecorded-buildturns. It keys off what the corpus SHIPS (trackedstory.json/ feature dir), so it cannot false-fail on an optional feature. A dropped artifact now fails hermetically in CI naming the exact file, long before it could surface as a live-replay hard-fail.
- HITL-first gate policy (field feedback). The declared project gate policy
(
project.gates) now defaults tointeractive(a human approves each gate).proxy(headless, Human Proxy approves) is a deliberate opt-in. A run-scoped--gatesflag no longer persists intosftdd-config.json(a single headless--gates proxyinvocation could permanently flip an interactive project); the drive resolves the effective mode per run as--gates ?? project.gatesand records it run-scoped only.proxywith no non-interactive signal (AUTO_CONTINUE/ CI) now refuses rather than silently auto-continuing.
- Consumer installs missing every
scripts/sftddbin (FEIP-7989, GH #168). The shippeddistwas incomplete, so agithub:...#tagconsumer install received a partial CLI set. The release now ships a completedistand guards it:prepare.mjsverifies every bin is present on a consumer install, the scaffoldedscripts/lkwarm-check refuses an incomplete kit, and adist-bins-shippedtest asserts parity. - Replay fell through to a live agent on a corpus miss. When a replay lane
was told to reproduce a turn the corpus lacked, the driver printed a note and
SPAWNED THE REAL AGENT, letting an agent "take over" a deterministic run and
masking an incomplete corpus. All three fall-throughs (design turn, build turn,
reflect verdict) now throw
ReplayCorpusMissError, failing loud (exit 2) and naming the missing artifact. No agent is ever spawned in a replay lane. .gitignoresilently dropped shipped files. An org-init*conf*.jsonglob matched any tracked file whose name contains "conf", so several shipped artifacts were never committed and a consumer never received them: scenario ac data (*-confirmed.json,*-nonconforming-*.json), a dropped corpus ac (AC3-confirmation-shown.json), and, most impactfully, the React client template'stsconfig.json(itsbuild/typecheckscripts aretsc --noEmit && vite build, so a scaffolded client could not typecheck or build without it). The ignore is now anchored to the actual runtime file (run-config.json) by exact basename, and every dropped shipped file is restored. Non-shipped local scenario recordings (stockflow-live/,stockflow-s3-selfheal-verify/, no trackedscenario.json) that the removal un-hid are kept out via exact directory paths (never a glob).- Replay/capture smoke harness gate policy. The harness is headless by
construction (it exports
LAKEBASE_SFTDD_HUMAN_PROXY=1), but it only passed--gates proxyon the capture path, relying on the old globalproxydefault for replay. With the HITL-first flip that default is gone, so a pure replay blocked at the first per-story spec gate. The harness now declares--gates proxyin both directions.
A deploy-verify failure caused by a shared-state-fragile prior test can now self-heal instead of dead-ending at the human gate. Surfaced by the live stockflow capture (F6/S3): three integrity-probe tests written for an earlier story asserted an absolute whole-table aggregate, so they passed on their own isolated build branch but failed the full-feature deploy-verify once later stories' rows shared the table. That is a fragile test, not broken software, but the only route was the terminal HIL, and re-driving just re-failed the same unscoped test.
- Deploy-verify self-heal routing (FEIP-7916). When a per-story deploy-verify fails, the deploy step re-runs the failing tests in ISOLATION on a fresh child branch. If they all pass alone, the failure is shared-state contamination, so instead of the terminal escalation the orchestrator records a one-shot marker and routes a story-level Navigator ASSESS-DEPLOY turn (confirm the fragile set, prescribe how to scope each to own its rows) then a Driver SCOPE-DEPLOY turn (refactor only those tests), and re-deploys to re-verify. A passing re-verify clears the marker and the story proceeds to acceptance. The self-heal is bounded to a single attempt: if the re-deploy still fails, the one shot is spent and it raises to the human, so a fragile test can never spin. A genuine regression (any failing test that still fails in isolation) takes the terminal gate exactly as before. Validated live end to end on the stockflow F6/S3 capture.
capture-scenario.shrelative--inputs-from. The path was consumed after the scriptcds into the freshly created project, so a relative--inputs-fromresolved against the project dir and the recorded intake vanished (the human-proxy "recorded source not found: .../intake/product-overview.md" refusal). It is now absolutized up front, and fails loud if the directory is missing.- Deterministic sprint-planning PROPOSE. The propose directive named its
artifact only in passing, so the Spec Author (an LLM) could invent candidates in
its reply, write no file, then on a re-dispatch claim it "already exists" , the
handoff guard then aborted the run on the empty artifact. The directive is now
explicit (WRITE
planning/feature-proposals.md, author it fresh), and in capture/replay (recorded feature-requests present) the propose step is DETERMINISTIC: a newconsort-human-proxy supply-proposalsprojects a conformingfeature-proposals.mdfrom the recorded requests instead of spawning the LLM. Interactive users keep the live propose turn.
Hardening surfaced by a live React-SPA capture: the SFTDD build now has a first-class client test lane, a SPA's e2e no longer collides with the backend's in CI, and the scaffolded full run can no longer green client code whose tests never ran. Plus a downstream-migrate matching fix.
- Client test lane for SFTDD (FEIP-7915). The test-list gains a
clientkind: the test-strategist routes client-verified ACs to it, the navigator authors them underclient/tests/, and the driver dispatches them to the client's Vitest/Playwright runners. The reflect gate is aware of the lane, so a client-verified AC can no longer be silently proven by a backend test.
- A React SPA owns its e2e lane (FEIP-7916). A SPA was scaffolded with two
e2e harnesses , the
client/Playwright suite and the backend's server-rendered Pythontests/e2elive_server , both binding the backend port, which collided in CI (reuseExistingServer:false) with "http://localhost:8000/health is already used".create-project/enable-e2enow make the client Playwright suite the sole e2e for a SPA (no Pythontests/e2e, no rootplaywright.configon a Node backend). - CI e2e port resiliency (FEIP-7916).
port_in_use+free_portare factored into a sharedscripts/port-utils.sh(run-dev.sh sources it);client/playwright.config.tstakesE2E_BACKEND_PORT/E2E_CLIENT_PORT(defaults 8000/5173) threaded into the uvicorn command, the/healthpoll, and the Vite proxy; andpr.ymlgains an "Allocate free E2E ports" preflight that moves off a stale port instead of hard-failing (multi-tenant safe). - The full run can no longer false-GREEN client code (FEIP-7915).
run-tests.shinstalls client deps and runs the client suite instead of silently skipping whenclient/node_modulesis absent, so a broken client test fails in-build, not at the deploy gate. discard --reviseresets the story's build state (FEIP-7915). Reviving a story now clears its cycle records + test-list statuses so the build lane genuinely re-drives instead of reading the stale build as allGreen.scm-merge --wait-migratematches the downstream run by merge-commit SHA instead of amergedAttime window, ending the false "(no matching run)" timeout whenmergedAtreflected a post-cleanup local clock.- Claiming resumes an in-flight feature instead of refusing (FEIP-7916).
The sprint driver re-claims each backlog feature right before driving it, so
resuming a sprint whose feature was mid-promote (
pr-ready/ci-green) failed with "Cannot claim feature branch from state pr-ready". The idempotent same-feature no-op now covers all in-flight claimed states, so a re-claim hands back the existing claim and the drive resumes where it stopped; a different feature in one of those states is stillalready-claimed-other. - Whole-table aggregate tests must own their state (FEIP-7916). A test that
asserts an absolute table-wide aggregate (an integrity probe / global
COUNT/SUM) passed on the isolated per-cycle build branch but failed the full-feature deploy-verify once other stories' rows shared the table. The test-strategist + navigator canon now requires scoping the seed AND the assertion to the test's own rows (or a delta), never a global total, enforced by a new spec-levelshared-state-aggregate-assertionsmell the reflect critic surfaces at design time.
A first-class React SPA client scaffold (a single-page app is now the path of least resistance for a UI project, not a build-from-scratch fight), plus two scaffold-hygiene fixes that every Databricks-internal adopter was hitting.
- First-class React SPA client scaffold (FEIP-7910).
templates/project/ client/ships a React 18 + TypeScript + Vite single-page app, layered intoapi/(the onlyfetchlayer),hooks/,components/,pages/, andstyles/(Databricks design tokens as CSS custom properties), with Vitest + Testing Library (jsdom) unit tests and a Playwright e2e example. A newclientFrameworkknob (create-project --client react|none) defaults toreactfor a--ui-trackproject andnoneotherwise, and is persisted tosftdd-config.json(project.clientFramework). The Python backend serves the built client fromclient/distin production (single-process deploy), the client's Vitest suite runs in CI (pr.yml) andrun-tests.sh, and the Architect and Driver now treat the React SPA as a first-classrenders_viapath, defaulting to it when a client was scaffolded. The dev/CI/hook plumbing keyed onclient/package.json(Vite boot, client build + Playwright, npm install) now has a real scaffold to find.
- scm-doctor no longer reports permanent false workflow drift (FEIP-7911).
detectWorkflowDriftnow substitutes{{LAKEBASE_KIT_VERSION}}in the template before comparing (the same substitution the writer and the command drift detector already applied), so a correctly version-pinned project reads asunchangedinstead of standing drift. - get-connection reference no longer trips the secret scanner (FEIP-7912).
The example DSN's fake
eyJ...JWT is replaced with a<jwt-token>placeholder, so the doc no longer blocks commits behind a Databricks secret-scan pre-commit hook.
Architect-canon design-lane optimization (FEIP-7902) plus a cluster of live-capture hardening fixes surfaced by a full stockflow F1+F6 sprint run.
- Architecture canon (FEIP-7902). The architect's cross-cutting standing
decisions (NFR posture, AC layer placement, persistence-invariant patterns) are
established once into a project-level
architecture/canon.json(twin ofconventions.json). Per-story architectural notes are then PROJECTED deterministically with no live architect turn in the common case; a live turn fires only when a story is novel, recovered through the existing revise-routing self-heal (architect-canon-gapsmell, owning role architect-reviewer). The establishing feature is exempt from gap-checking its own stories. - Durable per-turn timing.
consort-timingnow rolls up the driver's measuredturn.usagedurations by role and role/model (with cost), independent of stdout capture, plus a--skip-planningflag. The old inter-event gap rollups remain for orchestration-overhead analysis. - Multi-sprint capture.
capture-scenario.shaccepts repeatable--sprintgroups driven sequentially on one project; the backlog is scoped per sprint (sprints/<sprint>/requested.json), so a later sprint no longer re-drives an earlier sprint's feature.
- Apps mint their Lakebase DB credential at RUNTIME; no token is stored in
.env. Scaffolded Python and Node apps (and all-language migrations) build from connection metadata and mint a short-lived Postgres token on demand via the databricks CLI;.envcarries metadata only. An explicitDATABASE_URLstill overrides (CI/Docker). This fixes deployed apps and long runs failing once the old baked-in token expired. (Java/Kotlin Spring app-runtime minting is tracked separately as a follow-up.)
- The sprint halts on a raise-to-HIL instead of advancing. A feature that
escalates (e.g. a failed deploy-verify) now stops the sprint and exits non-zero,
rather than being counted "complete", which previously advanced to the next
sprint and crashed the next feature's claim with
already-claimed-other. story.jsonfield drift no longer hard-fails the feature-complete gate. The reconcile seam normalizes a strayfeaturetofeature_idand strips non-spec keys (e.g.status) intostory.schemaconformance.- Capture reconstitute only runs when a design-lane log exists. A fully-live capture (which designs straight into the project agent-log) no longer prints a spurious "requires --design-log" error.
Design-lane hardening surfaced by the live SFTDD capture (a fresh run that reached feature-complete but flagged two design defects along the way).
- A persistence invariant belongs to exactly one story (no cross-story
re-test). A declared
persistence_invariantis realized once by the feature's schema, so its fitness test belongs to the one story whose migration realizes it. A later story re-emitting a fitness item for an invariant an earlier story already covers is a redundant re-test that drifts (one copy asserts the field-named validation message, the other only the raw rejection) and dead-locks the reflect gate. NewcheckInvariantCoverageDistinct, wired into the test_list gate (gate-conformance-guard), hard-blocks Gate 3 on the duplicate. The reflect critic caught only the one duplicate phrased weaker; the deterministic gate catches all. Typesinvariant_idonTestListItemand adds the rule to the Test Strategist canon.
- The UX Designer's
design-guide.jsonconforms at the source, and the design lane gates on it. The UX Designer (told never to read the schema) drifted on shape (camelCase keys, a nestedspacing.scale, extra typography props) and, since the guide was gated only on existence, the non-conformant file rode all the way to the final feature drain. Thetypographyschema is expanded to hold the tokens the model legitimately produces (line_heights,font_weights; the numeric font uses the existingfont_mono), mapped to--line-height-*/--font-weight-*indesign-adherence. The UX Designer now gets the exact JSON shape inline in its prompt plus a response-formatter self-check, anddesignGuideReadyrequires the guide to EXIST and CONFORM (one shareddesignGuideConformancehelper backs both the self-check and the gate), so a malformed guide keeps the role pending instead of surfacing at the drain.
Follow-on to beta.8, surfaced by the live SFTDD capture at an S2 REFACTOR that dead-locked.
- A UI test that asserts the implementation instead of the seam no longer
dead-locks the REFACTOR. The Test Strategist authored a test that grepped the
page source for an inline
style=(e.g.text-align: right); the design lane then refactored that inline style into a token-driven class, so the test could never stay green and the REFACTOR halted. Newui-style-implementation-testsmell (spec-level, owned by the Test Strategist, re-gatestest_list) plus a reflect critic directive that flags a styling test asserting raw inline CSS in the page source rather than the rendered seam (a design-guide class /data-testid) or the design-adherence gate. The Test Strategist guidance now directs, for a design-guide-governed visual property, asserting the seam and leaving the rendered property to the design-adherence gate. This is a design-lane guard: it prevents the test from being authored, it does not retroactively rewrite a frozen test list.
Follow-on to beta.7, surfaced by the live SFTDD capture at the S1 experiment cut.
- Stray agent-created junk no longer blocks the experiment cut, and never rides a
commit. A design-lane agent wrote a mis-quoted file (named
") to the project root; the fork guard refused the cut because the build commit rangit add -Aand would have committed that junk onto the experiment branch.commitExperimentCodenow allow-lists what it stages (every tracked change anywhere, plus new untracked files only under the source/test/migration roots or with a recognized source extension, so root-levelapp.pyis still committed), andassertCleanForForkignores untracked files (uncommitted edits to tracked source still refuse). Adds anuntrackedtoggle toisDirtyand anuntrackedAllowoption tocommitAllIfChanged.
Resilience hardening surfaced by a live SFTDD capture run (paired-branch build + deploy + PR/CI).
- alembic subcommands can import app code (PYTHONPATH parity).
alembic upgraderunsenv.py(which prepends the project root tosys.path), butalembic history/headsdo not, yet still import every migration module to build the revision map. A data migration importing app code then failedModuleNotFoundErrorunder the migration-lineage check while working under upgrade.spawnAlembicnow puts the project root onPYTHONPATHfor every subcommand, and the scaffoldedalembic.inigains the idiomaticprepend_sys_path = .. - Ephemeral-verify branch create tolerates a silent client flake. A
create-branchthat exits non-zero yet lands the branch server-side no longer aborts:createBranchre-checks and adopts the landed branch.classifyDatabricksErrorfolds stdout + the exit code into the message when stderr is empty, so a silent failure is legible. - Ephemeral-verify child name capped at the 63-char Lakebase limit. An over-limit
name was truncated on create but looked up untruncated ("branch id not found"); the
name now truncates the descriptive prefix and preserves the unique
-vrfy-<nonce>suffix. - Pre-build reflection turn is guarded. A reflect turn that produces no readable
reflect-verdict.jsonnow escalates to the human instead of the driver silently re-invoking it into a stall.
migration-app-couplingsmell +consort-migration-cleangate. A deterministic check (mirroring contract-clean) that fails a build cycle when a migration imports app code at module scope, routing a repair to make the migration self-contained before it reaches CI.
EMU / CI robustness, surfaced by a partner project on an Enterprise Managed Users org.
- Host-aliased git remotes resolve to the correct owner/repo.
getGitHubUrlonly normalized a literalgit@github.com:remote, so an SSH host-alias remote (common for EMU, e.g.org-140212977@github-emu:databricks-field-eng/partner- asset-tracker.git) was mis-parsed into a garbage owner and every owner/repo API call 404'd , Create PR and self-hosted runner setup both failed with "Not Found". The normalizer now extracts owner/repo after any host (SCP,ssh://,https://, with or without a user) and re-homes it on github.com.
getActionsEnabled(ownerRepo), reports whether GitHub Actions is enabled for a repo (returnsundefinedwhen undeterminable, so a missing token never false-alarms). Used by the new scm-doctor check + the extension's Health Check.- scm-doctor
github-actions-disabledfinding , when Actions is off (commonly an org policy on EMU repos a repo admin cannot override), the kit's CI workflows (pr.yml / merge.yml) silently never run; the doctor now surfaces this explicitly with remediation instead of leaving "CI never happened" unexplained.
Greenfield hardening: scaffolded-project fixes surfaced by a hands-on evaluation of a freshly created project. All changes are additive and validated live on a real workspace (project provision + Flyway migrate + multi-schema diff + a full create-project run with a GitHub repo).
- Monorepo-aware migration-layout resolver (single source of truth).
scripts/lakebase/migration-layout.tsowns language detection and the migration path / pattern / glob conventions (detectLanguageAt,resolveMigrationLanguage,resolveMigrationLayout,compileMigrationPattern);schema-migratedelegates to it, and the VS Code extension consumes it so a subdir app (e.g.recipe-app/migrations) is detected correctly instead of assuming a repo-root Flyway layout. - Multi-schema schema diff (
--schema).branch-schema.tsnow owns one sharedbuildSchemaQuery/schemaObjectName/isAllSchemas; bothqueryBranchSchemaandgetSchemaDiffconsume it and accept aschemaargument (defaultpublic, a named schema, orall/*which qualifies names asschema.table).lakebase-schema-diffgains--schema <name|all>. Objects outsidepublicare no longer silently invisible to the diff. The schema name is bound, never interpolated. - Create-project preflight + cleanup (
scripts/lakebase/create-preflight.ts).checkDatabricksAuthruns as create Step 0: a missing/stale token fails with an actionabledatabricks auth login --host <host>message up front, before any GitHub repo or Lakebase project is created.warmAndVerifyKitwarms AND verifies the fast-CLI cache at create time and surfaces a specific warning if it fails (a silent warm failure used to surface later as a mysterious commit hang).withLakebaseRollbackwraps the post-create steps so a failure deletes the just-created Lakebase project, leaving no orphaned slug to block a same-name retry.
- Flyway baseline trap. The java + kotlin fallback
pom.xmland the dynamicpom-patch.tsnow pin<baselineVersion>0</baselineVersion>alongsidebaselineOnMigrate=true, somvn flyway:migrateappliesV1on a fresh database instead of consuming it as the baseline (parity with the kit's Flyway runner, which already passed-baselineVersion=0). pre-pushno longer blocks a push on a stale Databricks token. A failed OAuth token refresh now warns and lets the push proceed (the token only affects the downstream CI secret sync); it no longerexit 1s.- Commit-time schema diff can no longer stall a commit.
lkhonorsLK_NO_INSTALLso a cold kit cache skips the synchronousnpm install(the ~70s stall) and exits 97;prepare-schema-diff.shsets it and wraps the diff in a portable hard timeout (LAKEBASE_SCHEMA_DIFF_TIMEOUT, default 10s). A cold cache or slow Lakebase yields a commit without the diff, never a blocked commit.
- Artifact-root resolution + auto-migration. The on-disk artifact/log directory
name now lives in one place (
scripts/sftdd/sftdd-paths.ts:ARTIFACT_ROOT,resolveTddDir) instead of being a"./.tdd"default copy-pasted across ~20 call sites.resolveTddDiris dual-read: it prefers.sftdd, falls back to a legacy.tddwhen that is what exists, and defaults a fresh project to.sftdd. The orchestrator (consort-drive) auto-migrates a legacy.tddto.sftddon its next run (git mvto preserve history when possible, else a filesystem rename), and rewrites the project.gitignoreentries to match. - Per-story build granularity: contract/cleanup stories auto-drop to
ac. A story that drops, removes, or renames an existing shape (a column / endpoint / field, detected from the story id verb) now builds one verifiable increment per AC regardless of the run default, since its lockstep DB+code change is too heavy for one story-level GREEN turn (effectiveLoopForStoryinorchestrator-derive.ts, applied in both the routing and the RED/GREEN/REFACTOR prompt builder). - Reactive supersession self-heal. When greening an AC breaks PRIOR-story tests, a failed honest-GREEN verify routes a Navigator ASSESS turn that classifies each failure as supersession (the latest AC intentionally changed that behavior) or a genuine regression. Superseded tests are flagged for permissive Driver refactor; a regression routes one bounded Driver repair. A MIXED verdict (some superseded plus a regression) is served in a single repair turn (the repair directive now carries the superseded-tests allowlist).
- Ephemeral verify branch. With
LAKEBASE_SFTDD_EPHEMERAL_VERIFY=1, each GREEN / deploy verify runs its migrations + tests on a disposable child Lakebase branch forked off the story's experiment branch (then deleted), so a contract story's up/down migration fixtures cannot leave the shared DB half-migrated for the next run (scripts/sftdd/ephemeral-verify.ts). - Mid-turn context-overflow recovery. A role turn that overflows the model window
("prompt is too long") is retried on a fresh session (bounded), continuing from the
on-disk artifacts rather than failing the run (
scripts/sftdd/context-budget.ts). sftddEnvenv accessor.scripts/sftdd/sftdd-env.tsreadsLAKEBASE_SFTDD_*with a fallback to the legacyLAKEBASE_TDD_*name, the read-side half of the env-prefix rename below.
- Config file + env prefix renamed
tdd->sftdd(back-compat). The unified config is now.lakebase/sftdd-config.jsonand the runtime env knobs areLAKEBASE_SFTDD_*, matching theconsort-workflowsskill, thescripts/sftdd/dir, and theconsort-*bins. Both are dual-read:loadTddConfigpreferssftdd-config.jsonand falls back to a legacytdd-config.json; every env read goes throughsftddEnv, which falls back toLAKEBASE_TDD_*. Existing projects / shells keep working with no manual change; new writes use the canonical names. - Supersession + contract-completeness canon (
software-design-principles). Hard rule 8 (a later AC can supersede earlier tests) now requires scanning for supersession COMPREHENSIVELY, including FITNESS / migration tests that assert a property of a dropped shape (reversibility, schema-shape), not only tests that name it. Hard rule 9 makes a schema-contract change update the data model AND the code (ORM / queries / serializers / views) in lockstep with the migration. The Navigator ASSESS and Driver REPAIR prompts enforce both. - Artifact directory renamed
.tdd->.sftdd. New projects (and thesftdd-bootstraptemplate) scaffold a.sftdd/directory to match theconsort-workflowsnaming. Existing.tddprojects keep working (dual-read) and are auto-migrated on the next orchestrated run, so no manual action is required. Thetdd-paths.tsmodule was renamed tosftdd-paths.ts. - Spec Driven Development (SDD) framing.
consort-workflowsdocs now name the two lanes explicitly: the design lane (/design) is Spec Driven Development (SDD), which produces and freezes the executable spec at thespec+test_listgates; the build lane (/build) is Test Driven Development (TDD), which builds against that frozen spec. Narrative added to the skill README + SKILL, the spec-format reference, the design-lane agent prompts (spec-author, architect-reviewer, test-strategist), and the kit README + CLAUDE.
- Deploy resilience on a port already in use. Before refusing to deploy because a
port is occupied, the deploy stops its OWN prior app instance and re-probes, so a
re-run reclaims its own port instead of failing on a foreign-port refusal
(
scripts/sftdd/deploy.ts).
0.3.0-beta.1 - 2026-06-10
Second beta on the 0.3.0 line. Consume via
npx github:databricks-solutions/consort#v0.3.0-beta.1.
- Promote phase. After a feature is accepted, the deterministic driver runs a PR cycle (prepare-pr -> wait-ci -> HITL promote gate) then merges the feature up to its parent tier in git + Lakebase, so the next sprint forks from a populated parent.
- Universal turn recorder.
LAKEBASE_TDD_RECORD_DIRmakes the driver record every state-machine turn (design, build, gates, deploy, promote) as a replayable per-turn corpus:turns/<NNNN>-<label>/(manifest + the .tdd/code delta that turn produced) +turns/index.json, plus the cumulativerecorded-artifactsandrecorded-buildmirrors the existing replay engine consumes. - imports-clean gate (
consort-imports-clean): the app entry must import without an optional build artifact (e.g.client/dist) present, catching import-time coupling before deploy. Newimport-time-build-couplingbad smell plus a dev/prod-parity rule in thesoftware-design-principlescanon. - Claude Code plugin. The kit installs as a plugin:
claude plugin marketplace add databricks-solutions/consortthenclaude plugin install lakebase-app-dev-kit@lakebase-app-dev-kit. Launch the workflow with/lakebase-app-dev-kit:tdd(resumes in a scaffolded project, guides creation elsewhere). - Per-role agent runtime. Eight role agents (product-owner, spec-author,
ux-designer, architect-reviewer, test-strategist, navigator, driver,
release-engineer) scaffolded into a project's
.claude/agents/and invoked by the driver asclaude --agent <role>. - Per-story pipeline + experiments. Stories stream through design -> build with a ready queue; each story builds on its own experiment branch (cut / accept=merge / discard), paired with a Lakebase branch.
- Deterministic deploy + Release Engineer handoff at story acceptance, with deploy-evidence as the backstop; honest GREEN (no GREEN without a passing runner outcome) + escalate-to-HIL on any agent-surfaced error.
- Build commits working software at each GREEN + REFACTOR (code only, on the experiment branch), so accept's merge carries real commits to the feature branch and the promote phase opens a clean PR. CI now builds the client before tests (dev/CI parity).
- Agent-loop performance (P0-P7). Per-turn timing report
(
consort-timing), a leaner pre-digested REVIEW rubric, a fresh-per-story build session, a low-effort REVIEW turn, and reduced inter-phase shell overhead. - Kit CLIs resolve through a project's
scripts/lk(ref-keyed cache orLAKEBASE_KIT_DIR) instead of per-callnpxgit resolution. - The orchestrator is a deterministic state-machine driver
(
consort-drive), not an LLM agent.
0.3.0-beta.0 - 2026-06-05
First beta on the 0.3.0 line, graduating from the alpha series. Consume via
npx github:databricks-solutions/consort#v0.3.0-beta.0.
- Artifact-conformance gate. Per-artifact format registry: JSON artifacts are validated against their schema and narrative markdown against its required sections. The mock HITL approver hard-blocks a gate whose artifact exists but is malformed, rather than approving it.
- New schemas shipped in
dist:agent-log-event,architecture,design-guide,plan. Shared schema loader removes duplicated validation wiring. consort-gate-conformanceCLI to scan a feature's artifacts for conformance.- Structured agent logging. JSON-lines events (role, timestamp, level,
event) written to
.tdd/agent-log.jsonl, with theconsort-logCLI. HITL decisions are recorded (the mock reviewer validates expected elements and the human response is captured). - Per-role-agent contracts. Relay headers on every role agent; a new Spec Author (Business Analyst) role and a conditional UX Designer (UI-only) role with token-level design adherence enforced at the Playwright layer.
feature-request.mdartifact: the Feature Requester's original ask, the Spec Author's read-only input.
- Explicit artifact authorship.
spec.mdrenamed toproduct-overview.md(Product Owner, project-level), andfeature.{md,json}renamed tofeature-spec.{md,json}(Spec Author). "spec" is now reserved for the Spec Author. - NFRs moved off the spec-gated
feature-spec.json/story.jsonontoarchitecture.json(the architect proposes, the HIL adjudicates at Gate 2), removing spec-gate drift. - SCM feature-branch naming now goes through the shared sanitizer as the single
source of truth; claim preserves the canonical
feature_idcase and is idempotent.
- The Spec Author no longer overwrites the Feature Requester's original ask: the
requester's document is preserved as
feature-request.mdand never overwritten.